Пять принципов объектно-ориентированного проектирования в PHP

PHP

Пять принципов объектно-ориентированного проектирования: принцип единой ответственности, принцип изоляции интерфейса, принцип открытого-закрытого, принцип подстановки и принцип инверсии зависимостей. Эти принципы в основном изложены в книге Роберта С. Мартина «Гибкая разработка программного обеспечения — принципы, методы и практика» Эти пять принципов также лежат в основе 23 шаблонов проектирования.

Принцип единой ответственности Принцип единой ответственности, SRP

В среде MVC независимо от того, должны ли фильтрация и проверка безопасности полей базы данных, вставленных в форму, обрабатываться на уровне управления или на уровне модели, такие проблемы могут быть отнесены к сфере единой ответственности.

Единая ответственность имеет два значения:

  1. Избегайте распространения одних и тех же обязанностей на разные классы
  2. Один класс берет на себя слишком много обязанностей

Преимущества участия в SRP:

  1. уменьшить связь между классами
  2. Улучшение возможности повторного использования класса

Применение в реальной разработке кода: заводской режим, командный режим, прокси-режим и т. д. Фабричный шаблон (Factory) позволяет создавать экземпляры объектов при выполнении кода. Фабричный шаблон называется потому, что он отвечает за «производство» объектов. Если взять в качестве примера базу данных, то фабрике нужно генерировать разные экземпляры объектов в соответствии с разными параметрами. Он отвечает только за создание объекта, а не за конкретное содержание объекта.

Определите интерфейс адаптера:

<?php
interface DbAdapter 
{
    /**
    * 数据库连接
    * @param $config 数据库配置
    * @return resource
    */
    public function connect($config);
    
    /**
    * 执行数据库查询
    * @param string $query 数据库查询SQL字符串
    * @param mixed $handle 连接对象
    * @return resource
    */
    public function query($query, $handle);
}
?>

Определите класс операции с базой данных MySQL:

<?php
class DbAdapterMysql implements DbAdapter
{
    private $_dbLink; //数据库连接字符串表示

    /**
    * 数据库连接函数
    * @param $config 数据库配置
    * @throws DbException
    * @return resource
    */
    public function connect($config)
    {
        if($this->_dbLink = @mysql_connect($config->host .
            (empty($config->port) ? '' : ':' . $config->port),
        $config->user, $config->password, true)) {
            if(@mysql_select_db($config->database, $this->_dbLink)){
                if($config->charset){
                    mysql_query("SET NAMES '{$config->charset}'", $this->_dbLink);
                }
                return $this->_dbLink;
            }
        }
        //数据库异常
        throw new DbException(@mysql_error($this->_dbLink));
    }
    
    /**
    * 执行数据库查询
    * @param string $query 数据库查询SQL字符串
    * @param mixed $handle 连接对象
    * @return resource
    */
    public function query($query, $handle)
    {
        if ($resource = @mysql_query($query, $handle)) {
            return $resource;
        }
    }
}
?>

Класс работы с базой данных SQLite:

<?php
class DbAdapterSqlite implements DbAdapter
{
    private $_dbLink;
    
    /**
    * 数据库连接函数
    * @param $config 数据库配置
    * @throws DbException
    * @return resource
    */
    public function connect($config)
    {
        if ($this->_dbLink = sqlite_open($config->file, 0666, $error)) {
            return $this->_dbLink;
        }
        
        throw new DbException($error);
    }
    
    /**
    * 执行数据库查询
    * @param string $query 数据库查询SQL字符串
    * @param mixed $handle 连接对象
    * @return resource
    */
    public function query($query, $handle)
    {
        if ($resource = @sqlite_query($query, $handle)) {
            return $resource;
        }
    }
}
?>

Определите фабричный класс для создания необходимых классов в соответствии с различными переданными параметрами:

<?php
    class sqlFactory
    {
        public static function factory($type)
        {
            if (include_once 'Drivers/' . $type . '.php') {
                $classname = 'DbAdapter' . $type;
                return new $classname;
            } else {
                throw new Exception('Driver not found');
            }
        }
    }
?>

передача:

$db = sqlFactory::factory('MySQL');
$db = sqlFactory::factory('SQLite');

Шаблон команды разделяет обязанности «заказчика команды» и «исполнителя команды».

<?php
/**
* 厨师类,命令接受者与执行者
*/
class cook 
{
    public function meal(){
        echo '番茄炒鸡蛋',PHP_EOL;
    }
    
    public function drink(){
        echo '紫菜蛋花汤',PHP_EOL;
    }
    
    public function ok(){
        echo '完毕',PHP_EOL;
    }
}

/**
* 命令接口
*/
interface Command{
    public function execute();
}
?>

Смоделируйте процесс официанта и повара:

<?php
class MealCommand implements Command
{
    private $cook;
    
    //绑定命令接受者
    public function __construct(cook $cook){
        $this->cook = $cook;
    }
    
    public function execute(){
        $this->cook->meal();//把消息传递给厨师,让厨师做饭
    }
}

class DrinkCommand implements Command
{
    private $cook;
    
    //绑定命令接受者
    public function __construct(cook $cook){
        $this->cook = $cook;
    }
    
    public function execute(){
        $this->cook->drink();
    }
}
?>

Смоделируйте процесс клиента и официанта:

<?php
class cookControl
{
    private $mealCommand;
    private $drinkCommand;
    
    //将命令发送者绑定到命令接收器
    public function addCommand(Command $mealCommand, Command $drinkCommand){
        $this->mealCommand = $mealCommand;
        $this->drinkCommand = $drinkCommand;
    }
    
    public function callMeal(){
        $this->mealCommand->execute();
    }
    
    public function callDrink(){
        $this->drinkCommand->execute();
    }
}    
?>

Реализовать шаблон команды:

$control = new cookControl;
$cook = new cook;
$mealCommand = new MealCommand($cook);
$drinkCommand = new DrinkCommand($cook);
$control->addCommand($mealCommand, $drinkCommand);
$control->callMeal();
$control->callDrink();

Принцип разделения интерфейсов, Интернет-провайдер
Принцип разделения интерфейсов (ISP) гласит, что клиент не должен быть вынужден реализовывать какой-либо интерфейс, который он не будет использовать, но должен сгруппировать толстый интерфейс и заменить его несколькими интерфейсами, каждый из которых обслуживает подмодуль. Проще говоря, использование нескольких специализированных интерфейсов намного лучше, чем использование одного интерфейса.
Основная точка зрения провайдера:
1. Зависимость класса от другого класса должна основываться на минимальном интерфейсе.
Интернет-провайдеры могут добиться этого, не заставляя клиентов (пользователей интерфейса) зависеть от методов, которые они не используют, а реализующие классы интерфейса должны быть представлены только с одной ролью ответственности (в соответствии с принципами SRP).
Интернет-провайдеры могут уменьшить взаимодействие между клиентами — когда клиенту требуется новая ответственность (изменение требований), чтобы заставить интерфейс измениться, возможность влияния на других клиентов будет минимальной.
2. Клиентская программа не должна полагаться на методы (функции) интерфейса, которые ей не нужны.

Интернет-провайдер подчеркивает, что интерфейс обещает клиенту как можно меньше, и он должен быть конкретным.
Загрязнение интерфейса — это добавление к интерфейсу ненужных обязанностей.“接口隔离”其实就是定制化服务设计的原则. Используйте множественное наследование интерфейсов для реализации комбинирования различных интерфейсов, чтобы обеспечить внешние комбинированные функции — для достижения «услуг по запросу».

Для загрязнения интерфейса используйте следующие два метода:

  • Используйте делегирование для разделения интерфейсов.
  • Используйте множественное наследование для разделения интерфейсов.

В режиме делегирования два объекта участвуют в обработке одного и того же запроса, и объект, принимающий запрос, делегирует запрос другому объекту для обработки.Например, режим стратегии и режим прокси применяются к концепции делегирования.

принцип открыто-закрыто
С непрерывным увеличением масштабов программных систем сложность обслуживания и модификации программных систем продолжает расти.Эта дилемма побудила Бертрана Мейера, французского инженера, академика, предложить принцип «открытия-закрытия» (OCP) в 1998. , основная идея такова:
Поведение модуля Open (Open for extension) должно быть открытым и допускающим расширение, а не жестким.
Закрыто (Закрыто для модификации) не должно затрагивать или влиять на существующие программные модули в больших масштабах при расширении функции модуля.

Другими словами, требуется, чтобы разработчики реализовали расширение программной функции прикладной системы без изменения существующего функционального кода (исходного кода или двоичного кода) в системе. Если резюмировать одним предложением:一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的.
Открытость-закрытость может улучшить масштабируемость и ремонтопригодность системы, но это также относительно.
Взяв в качестве примера плеер, сначала определим абстрактный интерфейс:

interface Process
{
    public function process();
}

Затем расширьте этот интерфейс, чтобы реализовать функции декодирования и вывода:

class playerEncode implements Proess
{
    public function process(){
        echo "encode\r\n";
    }
}    

class playerOutput implements Process
{
    public function process(){
        echo "output\r\n";
    }
}

Для различных функций плеера он открыт здесь. Пока вы следуете соглашению и реализуете интерфейс процесса, вы можете добавлять в проигрыватель новые функциональные модули. Далее идет диспетчер планирования потоков, который определяет проигрыватель.После того как проигрыватель получает уведомление (внешнее поведение щелчка или внутреннее поведение уведомления), он вызывает фактическую обработку потока:

class playProcess
{
    private $message = null;
    public function __construct(){
    }
    
    public function callback(Event $event){
        $this->message = $event->click();
        if($this->message instanceof Process){
            $this->message->process();
        }
    }
}

Вышел конкретный продукт.Здесь определен класс MP4.Этот класс относительно закрытый,что определяет логику обработки события:

class MP4
{
    public function work(){
        $playProcess = new playProcess();
        $playProcess->callback(new Event('encode'));
        $playProcess->callback(new Event('output'));
    }
}

Наконец, класс обработки для сортировки событий, который отвечает за сортировку событий, оценку пользователя или внутреннего поведения, чтобы сгенерировать правильный «поток» для планирования встроенным диспетчером потоков проигрывателя:

class Event
{
    private $m;
    
    public function __construct($me){
        $this->m = $me;
    }
    
    public function click(){
        switch($this->m){
            case 'encode':
                return new playerEncode();
                break;
            case 'output':
                return new playerOutput();
                break;    
        }
    }
}

бегать:

$mp4 = new MP4;
$mp4->work();
//打印结果
encode
output

Как следовать принципу открытого-закрытого
Основная идея реализации открытого-закрытого заключается в том, что основной идеей абстрактного программирования является对抽象编程, а не конкретное программирование, потому что抽象相对稳定. Пусть класс зависит от фиксированной абстракции, такая модификация закрыта, а через объектно-ориентированный继承和多态механизм, который позволяет抽象体Наследование меняет собственное поведение, переопределяя его методы, и реализует новые методы расширения, поэтому оно открыто для расширения.
1. Полностью применить идеи «абстракции» и инкапсуляции в дизайне.
С одной стороны, это поиск различных возможных «переменных факторов» в программной системе и их инкапсуляция, с другой стороны, переменный фактор не должен быть разбросан по множеству разных модулей кода, а должен быть инкапсулирован в объект.
2. Применение в реализации программирования системных функций面向接口программирование.
При изменении требований может быть предоставлен новый класс реализации интерфейса для адаптации к изменениям.
Интерфейсно-ориентированное программирование требует, чтобы функциональные классы реализовывали интерфейсы, а объекты объявлялись как интерфейсные типы. В режиме дизайна режим оформления более явно использует OCP.

принцип замены
Определение и основная идея принципа подстановки, известного также как принцип подстановки Лискова (LSP), заключаются в следующем: систем, мы этого не делаем. Нет серьезного и рационального рассмотрения того, уместны ли отношения наследования между различными классами в прикладной системе и может ли производный класс правильно переопределить некоторые методы своего базового класса. Поэтому часто имеет место явление злоупотребления наследованием или неправильного наследования, что доставляет немало хлопот при последующем сопровождении системы.
ЛСП заявляет:子类型必须能够替换掉它们的父类型,并出现在父类能够出现的任何地方.
LSP в основном предназначен для наследования, наследование и деривация (полиморфизм) являются основными особенностями ООП.
Как соблюдать принципы проектирования LSP:

  • Методы родительского класса должны быть реализованы или переписаны в подклассе, а производный класс реализует только методы, объявленные в его абстрактном классе, и не должен давать избыточных определений или реализаций методов.

В клиентской программе следует использовать только объект родительского класса вместо объекта подкласса напрямую, чтобы можно было реализовать связывание во время выполнения (динамическое связывание). Если два класса A и B нарушают дизайн LSP, обычная практика заключается в создании нового абстрактного класса C в качестве надкласса двух конкретных классов и переносе общего поведения A и B в C, чтобы решить проблему. проблема поведения A и B. B — не совсем та же самая проблема.

Принцип обращения зависимости, DIP
Инверсия зависимостей просто означает преобразование зависимостей в зависимые интерфейсы Конкретные концепции заключаются в следующем:

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня, все они зависят от абстракции (родительские классы не могут зависеть от подклассов, все они зависят от абстрактных классов).
  • Абстрактное не может зависеть от конкретного, конкретное должно зависеть от абстрактного.

Зачем полагаться на интерфейсы? Поскольку интерфейс отражает абстракцию проблемы, а абстракция, как правило, относительно стабильна или меняется относительно редко, конкретность непостоянна. Следовательно, опора на абстракцию является основой для реализации расширения кода и привязки времени выполнения (полиморфизма): пока реализованы подклассы абстрактного класса, они могут использоваться потребителями класса.

<?php
interface employee
{
    public function working();
}

class teacher implements employee
{
    public function working(){
        echo 'teaching...';
    }
}

class coder implements employee
{
    public function working(){
        echo 'coding...';
    }
}

class workA
{
    public function work(){
        $teacher = new teacher;
        $teacher->working();
    }
}

class workB
{
    private $e;
    public function set(employee $e){
        $this->e = $e;
    }
    
    public function work(){
        $this->e->working();
    }
}

$worka = new workA;
$worka->work();
$workb = new workB;
$workb->set(new teacher());
$workb->work();

В workA метод работы зависит от реализации учителя, в workB вместо этого работа опирается на абстракцию, так что требуемые объекты могут быть переданы через параметры.
В workB экземпляр учителя передается через метод установки, таким образом реализуя фабричный шаблон. Поскольку реализация жестко закодирована, чтобы еще больше расширить код, запишите эту зависимость в файле конфигурации, указав, что workB нуждается в объекте учителя, и программа специально определяет, правильна ли конфигурация (например, является ли файл зависимого класса правильно это или нет), существование) и реализация, от которой зависит конфигурация загрузки, эта программа обнаружения называется IOC-контейнером.
МОК (инверсия управления) является синонимом принципа инверсии зависимостей (DIP). Внедрение зависимостей (DI) и поиск зависимостей (DS) — это две реализации IOC.