Все для создания и продвижения сайтов

Верстка, программирование, SEO

Шаблоны проектирования в PHP

Сейчас мы поговорим о шаблонах проектирования, а если точнее, то о шаблонах проектирования интернет-приложений на PHP. Профессиональные разработчики вряд ли узнают что-то новое, но для новичков данная статья будет очень полезна.

Что такое шаблоны проектирования?

Это вовсе не аналитические шаблоны, не описание стандартных структур информации вроде связных перечней. Также это не правила, по которым строятся определенные интерфейсы и приложения.

Шаблоны проектирование - это:

  • фактически является «описанием взаимодействия классов и объектов, построенным для решения типичных задач проектирования в конкретном контексте». Иными словами, шаблоны проектирования предоставляют весь набор подходов к решению различных каждодневных проблем.
  • не класс или библиотека, которые можно бы было просто включить в имеющуюся систему, это не определенный алгоритм, который можно бы было преобразовать в код.
  • что-то большее, они описывают способы, посредством которых может быть спроектирована любая программная система, которая решает проблему конкретного рода.
  • значительно ускоряют разработку приложений, так как собой представляют проверенные практикой решения, которые нужно лишь воплотить в определенной архитектуре. Кроме того, шаблоны проектирования очень упрощают понимание достаточно сложных концепций.

Разумеется, следует в выборе шаблона проектирования для решения каждой определенной проблемы проявлять осмотрительность.

«Каждый из шаблонов описывает одну из тех проблем, возникающих раз за разом. И потом описывает основу решения данной проблемы так, что вы можете воплотить такое решение во множестве программ, при этом ни разу не повторившись.», К. Александер.

Виды шаблонов проектирования

В настоящее время существует 23 шаблона проектирования, в соответствии с их назначением они разделены на три группы:

  1. порождающие шаблоны: применяются для создания объектов, которые потом могут использоваться независимо от системы, создавшей их:
    • Абстрактная фабрика (имена производимых объектов).
    • Строитель (соотношение элементов сложных объектов).
    • Фабричный метод (подкласс создаваемых объектов).
    • Прототип (класс создаваемых объектов).
    • Одиночка (класс имеет единственную инстанцию).
  2. структурные шаблоны: объединяют в одну логическую структуру объекты, несвязанные друг с другом:
    • Адаптер (интерфейс к объекту).
    • Мост (реализация объекта).
    • Компоновщик (структура и соотношение элементов объекта).
    • Декоратор (ответственность объекта такого же класса).
    • Фасад (интерфейс к подсистеме объекта).
    • Приспособленец (подсистема хранения объекта).
    • Заместитель (способ и место обращения к объекту).
  3. поведенческие шаблоны: управляют отношениями, алгоритмами и распределением обязанностей между различными программными объектами:
  4. Посетитель (операции, которые осуществляются с объектом, без изменения его класса).
  5. Хранитель (какие частные данные класса хранятся вне его, и когда).
  6. Шаблонный метод (последовательность действий).
  7. Стратегия (выбор алгоритма).
  8. Состояние (состояние объектов).
  9. Наблюдатель (число зависимых объектов и метод обновления их состояния).
  10. Посредник (порядок и доступ взаимодействия объектов друг с другом).
  11. Интератор (порядок доступа к вложенным частям).
  12. Цепочка ответственности (выбор объекта, который способен выполнить этот запрос).
  13. Команда (способ и время выполнения запроса).

Основные шаблоны проектирования в php

А теперь мы расскажем вам подробнее о наиболее востребованных из вышеперечисленных шаблонов.

Одиночка

<?php

final class Product
{

    private static $instance;

    public $mix;

    public static function getInstance() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
    }

    private function __clone() {
    }
}

$firstProduct = Product::getInstance();
$secondProduct = Product::getInstance();

$firstProduct->mix = 'test';
$secondProduct->mix = 'example';

print_r($firstProduct->mix);
print_r($secondProduct->mix);

Пул одиночек

В определенном случае может потребоваться обеспечить доступ к паре одиночек в одном проекте.

<?php

abstract class FactoryAbstract {

    protected static $instances = array();

    public static function getInstance() {
        $className = static::getClassName();
        if (!(self::$instances[$className] instanceof $className)) {
            self::$instances[$className] = new $className();
        }
        return self::$instances[$className];
    }

    public static function removeInstance() {
        $className = static::getClassName();
        if (array_key_exists($className, self::$instances)) {
            unset(self::$instances[$className]);
        }
    }

    final protected static function getClassName() {
        return get_called_class();
    }

    protected function __construct() { }
    final protected function __clone() { }
}

abstract class Factory extends FactoryAbstract {

    final public static function getInstance() {
        return parent::getInstance();
    }

    final public static function removeInstance() {
        parent::removeInstance();
    }
}
// Использование:

class FirstProduct extends Factory {
    public $a = [];
}
class SecondProduct extends FirstProduct {
}

FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;

print_r(FirstProduct::getInstance()->a);
// array(1, 3)
print_r(SecondProduct::getInstance()->a);
// array(2, 4)

Стратегия

Реализуя этот шаблон, вы инкапсулируете какую-то группу алгоритмов таким образом, чтобы порожденный класс мог воспользоваться ими, при этом ничего не зная об их конкретной реализации.

<?php

interface OutputInterface {
    public function load();
}

class SerializedArrayOutput implements OutputInterface {
    public function load() {
        return serialize($arrayOfData);
    }
}

class JsonStringOutput implements OutputInterface {
    public function load() {
        return json_encode($arrayOfData);
    }
}

class ArrayOutput implements OutputInterface {
    public function load() {
        return $arrayOfData;
    }
}

Декоратор

Данный шаблон подразумевает внедрение совсем нового поведения в объект в момент выполнения программы.

<?php
class HtmlTemplate {
    // методы родительского класса
}

class Template1 extends HtmlTemplate {
    protected $_html;

    public function __construct() {
        $this->_html = "

__text__

"; } public function set($html) { $this->_html = $html; } public function render() { echo $this->_html; } } class Template2 extends HtmlTemplate { protected $_element; public function __construct($s) { $this->_element = $s; $this->set("

" . $this->_html . "

"); } public function __call($name, $args) { $this->_element->$name($args[0]); } } class Template3 extends HtmlTemplate { protected $_element; public function __construct($s) { $this->_element = $s; $this->set("" . $this->_html . ""); } public function __call($name, $args) { $this->_element->$name($args[0]); } }

Фабрика

Это еще один очень популярный шаблон. Он производит инстанции объектов.

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

<?php

interface Factory {
    public function getProduct();
}

interface Product {
    public function getName();
}

class FirstFactory implements Factory {

    public function getProduct() {
        return new FirstProduct();
    }
}

class SecondFactory implements Factory {

    public function getProduct() {
        return new SecondProduct();
    }
}

class FirstProduct implements Product {

    public function getName() {
        return 'The first product';
    }
}

class SecondProduct implements Product {

    public function getName() {
        return 'Second product';
    }
}

$factory = new FirstFactory();
$firstProduct = $factory->getProduct();
$factory = new SecondFactory();
$secondProduct = $factory->getProduct();

print_r($firstProduct->getName());
// первый продукст
print_r($secondProduct->getName());
// второй продукт

Абстрактная фабрика

Бывает так, что когда у нас есть несколько похожих фабрик, и нам необходимо скрыть всю логику выбора фабрики для каждого определенного запроса. Здесь нам поможет именно этот шаблон.

<?php

class Config {
    public static $factory = 1;
}

interface Product {
    public function getName();
}

abstract class AbstractFactory {

    public static function getFactory() {
        switch (Config::$factory) {
            case 1:
                return new FirstFactory();
            case 2:
                return new SecondFactory();
        }
        throw new Exception('Bad config');
    }

    abstract public function getProduct();
}

class FirstFactory extends AbstractFactory {
    public function getProduct() {
        return new FirstProduct();
    }
}
class FirstProduct implements Product {
    public function getName() {
        return 'The product from the first factory';
    }
}

class SecondFactory extends AbstractFactory {
    public function getProduct() {
        return new SecondProduct();
    }
}
class SecondProduct implements Product {
    public function getName() {
        return 'The product from second factory';
    }
}

$firstProduct = AbstractFactory::getFactory()->getProduct();
Config::$factory = 2;
$secondProduct = AbstractFactory::getFactory()->getProduct();

print_r($firstProduct->getName());
// Первый продукт из первой фабрики
print_r($secondProduct->getName()); 

// Второй продукт из второй фабрики

Наблюдатель

«Наблюдатель» – шаблон, который предусматривает для наблюдаемого объекта полную возможность зарегистрировать наблюдателя, когда тот вызовет особый метод.

Затем, если состояние наблюдаемого объекта будет меняться, он посылает об этом сообщение своим наблюдателям.

<?php

interface Observer {
  function onChanged($sender, $args);
}

interface Observable {
  function addObserver($observer);
}

class CustomerList implements Observable {
  private $_observers = array();

  public function addCustomer($name) {
    foreach($this->_observers as $obs)
      $obs->onChanged($this, $name);
  }

  public function addObserver($observer) {
    $this->_observers []= $observer;
  }
}

class CustomerListLogger implements Observer {
  public function onChanged($sender, $args) {
    echo( "'$args' Customer has been added to the list \n" );
  }
}

$ul = new UserList();
$ul->addObserver( new CustomerListLogger() );
$ul->addCustomer( "Jack" );_observers as $obs)
      $obs->onChanged($this, $name);
  }

  public function addObserver($observer) {
    $this->_observers []= $observer;
  }
}

class CustomerListLogger implements Observer {
  public function onChanged($sender, $args) {
    echo( "'$args' Customer has been added to the list \n" );
  }
}

$ul = new UserList();
$ul->addObserver( new CustomerListLogger() );
$ul->addCustomer( "Jack" );

Адаптер

«Адаптер» позволяет надстроить интерфейс над классом, чтобы применять его в системе, которая использует другие соглашения вызова.

<?php

class SimpleBook {

    private $author;
    private $title;

    function __construct($author_in, $title_in) {
        $this->author = $author_in;
        $this->title  = $title_in;
    }

    function getAuthor() {
        return $this->author;
    }

    function getTitle() {
        return $this->title;
    }
}

class BookAdapter {

    private $book;

    function __construct(SimpleBook $book_in) {
        $this->book = $book_in;
    }
    function getAuthorAndTitle() {
        return $this->book->getTitle().' by '.$this->book->getAuthor();
    }
}

// использование
$book = new SimpleBook("Gamma, Helm, Johnson, and Vlissides", "Design Patterns");
$bookAdapter = new BookAdapter($book);
echo 'Author and Title: '.$bookAdapter->getAuthorAndTitle();

function echo $line_in) {
  echo $line_in."
"; }

Поздняя инициализация

Представьте себе ситуацию: при создании инстанции объекта вы пока не знаете, какие из функций потребуются объекту в будущем, а какие из них – нет.

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

<?php

interface Product {
    public function getName();
}

class Factory {

    protected $firstProduct;
    protected $secondProduct;

    public function getFirstProduct() {
        if (!$this->firstProduct) {
            $this->firstProduct = new FirstProduct();
        }
        return $this->firstProduct;
    }

    public function getSecondProduct() {
        if (!$this->secondProduct) {
            $this->secondProduct = new SecondProduct();
        }
        return $this->secondProduct;
    }
}

class FirstProduct implements Product {
    public function getName() {
        return 'The first product';
    }
}

class SecondProduct implements Product {
    public function getName() {
        return 'Second product';
    }
}

$factory = new Factory();

print_r($factory->getFirstProduct()->getName());
// The first product
print_r($factory->getSecondProduct()->getName());
// Second product
print_r($factory->getFirstProduct()->getName());
// The first product

Цепочка ответственности

В основе этого шаблона – серия обработчиков событий, которые передают сообщения по цепочке.

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

<?php
interface Command {
    function onCommand($name, $args);
}

class CommandChain {
    private $_commands = array();

    public function addCommand($cmd) {
        $this->_commands[]= $cmd;
    }

    public function runCommand($name, $args) {
        foreach($this->_commands as $cmd) {
            if ($cmd->onCommand($name, $args))
                return;
        }
    }
}

class CustCommand implements Command {
    public function onCommand($name, $args) {
        if ($name != 'addCustomer')
            return false;
        echo("This is CustomerCommand handling 'addCustomer'\n");
        return true;
    }
}

class MailCommand implements Command {
    public function onCommand($name, $args) {
        if ($name != 'mail')
            return false;
        echo("This is MailCommand handling 'mail'\n");
        return true;
    }
}

$cc = new CommandChain();
$cc->addCommand( new CustCommand());
$cc->addCommand( new MailCommand());
$cc->runCommand('addCustomer', null);
$cc->runCommand('mail', null);

Пул объектов

«Пул объектов» – еще одна хэш-таблица. Все объекты в нее помещаются сразу после инициализации и затем извлекаются по мере потребности.

<?php
class Product {

    protected $id;

    public function __construct($id) {
        $this->id = $id;
    }

    public function getId() {
        return $this->id;
    }
}

class Factory {

    protected static $products = array();

    public static function pushProduct(Product $product) {
        self::$products[$product->getId()] = $product;
    }

    public static function getProduct($id) {
        return isset(self::$products[$id]) ? self::$products[$id] : null;
    }

    public static function removeProduct($id) {
        if (array_key_exists($id, self::$products)) {
            unset(self::$products[$id]);
        }
    }
}

Factory::pushProduct(new Product('first'));
Factory::pushProduct(new Product('second'));

print_r(Factory::getProduct('first')->getId());
// first
print_r(Factory::getProduct('second')->getId());
// second

Прототип

Иногда инициализация определенных объектов может проходить в несколько этапов. Было бы разумно сэкономить ресурсы на первом этапе.

Для этого создается «прототип» – заранее инициализированный и сохраненный объект, который потом может быть клонирован и полностью инициализирован.

<?php
interface Product {
}

class Factory {

    private $product;

    public function __construct(Product $product) {
        $this->product = $product;
    }

    public function getProduct() {
        return clone $this->product;
    }
}

class SomeProduct implements Product {
    public $name;
}

$prototypeFactory = new Factory(new SomeProduct());

$firstProduct = $prototypeFactory->getProduct();
$firstProduct->name = 'The first product';

$secondProduct = $prototypeFactory->getProduct();
$secondProduct->name = 'Second product';

print_r($firstProduct->name);
// The first product
print_r($secondProduct->name);
// Second product

Строитель

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

<?php
class Product {

    private $name;

    public function setName($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Builder {

    protected $product;

    final public function getProduct() {
        return $this->product;
    }

    public function buildProduct() {
        $this->product = new Product();
    }
}

class FirstBuilder extends Builder {

    public function buildProduct() {
        parent::buildProduct();
        $this->product->setName('The product of the first builder');
    }
}

class SecondBuilder extends Builder {

    public function buildProduct() {
        parent::buildProduct();
        $this->product->setName('The product of second builder');
    }
}

class Factory {

    private $builder;

    public function __construct(Builder $builder) {
        $this->builder = $builder;
        $this->builder->buildProduct();
    }

    public function getProduct() {
        return $this->builder->getProduct();
    }
}

$firstDirector = new Factory(new FirstBuilder());
$secondDirector = new Factory(new SecondBuilder());

print_r($firstDirector->getProduct()->getName());
// The product of the first builder
print_r($secondDirector->getProduct()->getName());
// The product of second builder
Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.