Паттерны ООП с примерами и описанием

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

Singleton (одиночка)

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

class Singleton
{
    private static $instance = null;

    private function __construct(){ /* ... @return Singleton */ }  // Защищаем от создания через new Singleton

    private function __clone()    { /* ... @return Singleton */ }  // Защищаем от создания через клонирование

    private function __wakeup()   { /* ... @return Singleton */ }  // Защищаем от создания через unserialize
 
    public static function getInstance() {
        if (is_null(self::$instance)) {
            self::$instance = new self;
        }
        return self::$instance;
    }
}

Registry (реестр, журнал записей)

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

class Registry
{
    private $registry = array();
 
    public function set($key, $object) {
        $this->registry[$key] = $object;
    }
 
    public function get($key) {
        return $this->registry[$key];
    }
}

Singleton Registry (одинокий реестр) - не путайте с Registry Singleton (реестр одиночек)

«Реестр» нередко является «одиночкой», однако это не всегда должно быть именно так. Например мы можем заводить в бухгалтерии несколько журналов, в одном работники от «А» до «М», в другом от «Н» до «Я». Каждый такой журнал будет «реестром», но не «одиночкой», потому как журналов уже 2.

class SingletonRegistry
{
    private static $instance = null;
 
    private $registry = array();

    private function __construct(){ /* ... @return Singleton */ }  // Защищаем от создания через new Singleton

    private function __clone()    { /* ... @return Singleton */ }  // Защищаем от создания через клонирование

    private function __wakeup()   { /* ... @return Singleton */ }  // Защищаем от создания через unserialize
 
    public static function getInstance() {
        if (is_null(self::$instance)) {
            self::$instance = new self;
        }
        return self::$instance;
    }
 
    public function set($key, $object) {
        $this->registry[$key] = $object;
    }
 
    public function get($key) {
        return $this->registry[$key];
    }
}

Multiton (пул «одиночек») или другими словами Registry Singleton (реестр одиночек) - не путайте с Singleton Registry (одинокий реестр)

Нередко «реестр» служит именно для хранения «одиночек». Но, т.к. паттерн «реестр» не является «порождающим паттерном» а хотелось бы рассматривать «реестр» во взаимосвязи с «одиночкой»Поэтому придумали паттерн Multiton, который по своей сути является «реестром» содержащий несколько «одиночек», каждый из которых имеет своё «имя» по которому к нему можно получить доступ.

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

class Database
{
    private static $instances = array();

    private function __construct() { }
    private function __clone() { }

    public static function getInstance($key) {

        if(!array_key_exists($key, self::$instances)) {
            self::$instances[$key] = new self();
        }
        return self::$instances[$key];
    }
}

$master = Database::getInstance('master');
var_dump($master); // object(Database)#1 (0) { }

$logger = Database::getInstance('logger');
var_dump($logger); // object(Database)#2 (0) { }

$masterDupe = Database::getInstance('master');
var_dump($masterDupe); // object(Database)#1 (0) { }

// Fatal error: Call to private Database::__construct() from invalid context
$dbFatalError = new Database();

// PHP Fatal error:  Call to private Database::__clone()
$dbCloneError = clone $masterDupe;

Object pool (пул объектов)

По сути данный паттерн является «реестром», который хранит только объекты, никаких строк, массивов и т.п. типов данных.

Factory (фабрика)

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

abstract class AnimalAbstract 
{
    protected $species;

    public function getSpecies() {
        return $this->species;
    }
}

class Cat extends AnimalAbstract 
{
    protected $species = 'cat';
}

class Dog extends AnimalAbstract 
{
    protected $species = 'dog';
}

class AnimalFactory 
{
    public static function factory($animal) 
    {
        switch ($animal) {
            case 'cat':
                $obj = new Cat();
                break;
            case 'dog':
                $obj = new Dog();
                break;
            default:
                throw new Exception("Animal factory could not create animal of species '" . $animal . "'", 1000);
        }
        return $obj;
    }
}

$cat = AnimalFactory::factory('cat'); // object(Cat)#1
echo $cat->getSpecies(); // cat

$dog = AnimalFactory::factory('dog'); // object(Dog)#1
echo $dog->getSpecies(); // dog

$hippo = AnimalFactory::factory('hippopotamus'); // This will throw an Exception

Хочется обратить внимание, что метод factory также представляет собой паттерн, его называют Factory method (фабричный метод).

Builder (строитель)

Итак, мы уже поняли, что «Фабрика» — это автомат по продаже напитков, в нем уже есть всё готовое, а Вы только говорите что вам нужно. «Строитель» — это завод, который производит эти напитки и содержит в себе все сложные операции и может собирать сложные объекты из более простых (упаковка, этикетка, вода, ароматизаторы и т.п.) в зависимости от запроса.

class Bottle
{
    public $name;
    public $liters;
}


/**
 * все строители должны
 */
interface BottleBuilderInterface
{
    public function setName();
    public function setLiters();
    public function getResult();
}

class CocaColaBuilder implements BottleBuilderInterface
{
    private $bottle;

    public function __construct()
    {
        $this->bottle = new Bottle();
    }

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

    public function setLiters($value)
    {
        $this->bottle->liters = $value;
    }

    public function getResult()
    {
        return $this->bottle;
    }
}
$juice = new CocaColaBuilder();
$juice->setName('Coca-Cola Light');
$juice->setLiters(2);
$juice->getResult();

Prototype (прототип)

Напоминая «фабрику», он также служит для создания объектов, однако с немного другим подходом. Представьте себя в баре, Вы пили пиво и оно у Вас закончивается, Вы говорите бармену - сделай мне еще одно такое же. Бармен в свою очередь смотрит на пиво, которое Вы пьете и делает копию, как Вы попросили. В php уже есть реализация такого паттерна, она называется clone.

$newJuice = clone $juice;

Lazy initialization (отложенная инициализация)

Например, начальник видит список отчетов по разным видам деятельности и думает, что эти отчеты уже есть, но на самом выводятся только названия отчетов, а сами отчеты еще не сформированы, и будут формироваться только по приказу (например по нажатию кнопки Просмотреть отчет). Частный случай ленивой инициализации — создание объекта в момент обращения к нему. На википедии можно найти интересный, но не совсем правильный пример, т.к. согласно теории, правильным примером в php будет например функция __autoload

Dependency injection (внедрение зависимости)

Внедрение зависимости позволяет переложить часть ответственности за какой-то функционал на другие объекты. Например если нам требуется нанять новый персонал, то мы можем не создавать свой отдел кадров, а внедрить зависимость от компании по подбору персонала, которая свою очередь по первому нашему требованию «нам нужен человек», будет либо сама работать как отдел кадров, либо же найдет другую компанию (при помощи «локатора служб»), которая предоставит данные услуги.
«Внедрение зависимости» позволяет перекладывать и взаимозаменять отдельные части компании без потери общей функциональности.

Class AppleJuice {}

// этот метод представляет собой примитивную реализацию паттерна Dependency injection и дальше Вы в этом убедитесь
function getBottleJuice(){

	$obj = new AppleJuice;

	// проверим объект, а то вдруг нам подсунули пиво (пиво ведь не сок)
	if(AppleJuice $obj){
		return $obj;
	}
}

$bottleJuice = getBottleJuice();

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

Class AppleJuice {}
Class OrangeJuice {}

// этот метод реализовывает Dependency injection
function getBottleJuice(){

	$obj = new OrangeJuice;

	// проверим объект, а то вдруг нам подсунули пиво (пиво ведь не сок)
	if(OrangeJuice $obj){
		return $obj;
	}
}

Как видите, нам пришлось изменить не только вид сока, но и проверку на вид сока, не очень то удобно. Гораздо правильнее использовать принцип Dependency inversion:

Interface Juice {}
Class AppleJuice implements Juice {}
Class OrangeJuice implements Juice {}

function getBottleJuice(){

	$obj = new OrangeJuice;

	// проверим объект, а то вдруг нам подсунули пиво (пиво ведь не сок)
	if(Juice $obj){
		return $obj;
	}
}

Dependency inversion иногда путают с Dependency injection, но путать их не нужно, т.к. Dependency inversion это принцип, а не паттерн.

Service Locator (локатор служб)

«Локатор служб» является методом реализации «внедрения зависимости». Он возвращает разные типы объектов в зависимости от кода инициализации. Пускай задача стоит доставить наш пакет сока, созданный строителем, фабрикой или ещё чем, куда захотел покупатель. Мы говорим локатору «дай нам службу доставки», и просим службу доставить сок по нужному адресу. Сегодня одна служба, а завтра может быть другая. Нам без разницы какая это конкретно служба, нам важно знать, что эта служба доставит то, что мы ей скажем и туда, куда скажем. В свою очередь службы реализуют интерфейс «Доставить <предмет> на <адрес>».

Если говорить о реальной жизни, то наверное хорошим примером Service Locator-а может быть php-расширение PDO, т.к. сегодня мы работаем с базой данных MySQL, а завтра можем работать с PostgreSQL. Как Вы уже поняли, нашему классу не важно в какую базу данных отправлять свои данные, важно, что он может это делать.

$db = new PDO('mysql:dbname=test;host=localhost', $user, $pass);
$db = new PDO('pgsql:dbname=test host=localhost', $user, $pass);

Отличие Dependency injection от Service Locator

Если Вы еще не заметили, то хочется пояснить. Dependency injection в результате возвращает не сервис (которым можно что-то куда-то доставить) а объект, данные которого использует. 

Источники: 1 - 2 - 3 - 4

Оцени публикацию:
  • 10,45
Оценили человек: 10

Похожие статьи:

Справочники и учебники:


Предложения и пожелания:
Ваше имя:
Ваш E-mail:
Сколько будет Οдин + Τри
Главная
X

youtube.com/watch?v=7hFivbgIEqk

При полном или частичном использовании материалов данного сайта, ссылка на сайт "yapro.ru" обязательна как на источник информации.
Автоматический импорт материалов и информации с сайта запрещен.
Copyrights © 2007 - 2017 YaPro.Ru

Главная » Веб-мастеру » PHP »