7 важнейших паттернов программирования (проектирования)

10.08.2019

Все примеры написаны на php, где -> вызов метода объекта, :: вызов статичного метода.

1. Синглтон (Singleton)

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

class Registry {
    private static $instance = null;

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

        return self::$instance;
    }

    public function setValue($key, $value) { ... }
    public function getValue($key) { ... }
}

...

Registry::getInstance()->setValue('exchangeRate', 65.41)
$rate = Registry::getInstance()->getValue('exchangeRate');

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

2. Фабрика (Factory)

Обычная фабрика производит товары. Программная фабрика производит объекты. Суть в том, что создание объектов происходит без указания какого конкретного класса они должны быть производными. Чтобы обеспечить это, создание объектов происходит вызовом специального метода (чаще статичного) вместо прямого вызова конструктора класса.

abstract class Coffee {
    public static function factory($type) {
        $className = "Coffee{$type}";
        return new $className();
    }

    abstract public function milkFoamLevel();
}

class CoffeeCappuccino extends Coffee {
    public function milkFoamLevel() {
        return 'normal';
    }
}

class CoffeeEspresso extends Coffee {
    public function milkFoamLevel() {
        return 'none';
    }
}

...

$coffeeCappuccino = Coffee::factory('Cappuccino');
$coffeeEspresso = Coffee::factory('Espresso');

echo($coffeeCappuccino->milkFoamLevel());   // normal
echo($coffeeEspresso->milkFoamLevel());     // none

3. Стратегия (Strategy)

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

class SecureProtocolStrategy {
    public function getUrl($domain) {
        return "https://{$domain}/";
    }
}

class NoSecureProtocolStrategy {
    public function getUrl($domain) {
        return "http://{$domain}/";
    }
}

class UrlBuilder {
    private $protocolStrategy;

    public function setDomain($domain) { ... }

    public function setProtocolStrategy($strategy) {
        $this->protocolStrategy = $strategy;
    }

    public function getResult() {
        return $this->protocolStrategy->getUrl($this->domain);
    }
}

...

$url = new UrlBuilder();
$url->setDomain("info.projectpulse.ru");

if($useSSL){
    $url->setProtocolStrategy(new SecureProtocolStrategy());
}else{
    $url->setProtocolStrategy(new NoSecureProtocolStrategy());
}

$result = $url->getResult();

4. Наблюдатель (Observer)

Используется в связях между объектами один-ко-многим, так что при изменении состояния одного объекта, все зависимые объекты получают уведомления об этом. Уведомление обычно реализуется простым вызовом определённого метода у зависимых объектов.

class ExchangeRate {
    static private $instance;
    private $observers = [];

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

    public function getRate() { ... }
    public function updateRate($rate) {
        $this->rate = $rate;
        $this->notifyObservers();
    }

    public function newObserver($observer) {
        $this->observers[] = $observer;
    }

    private function notifyObservers() {
        foreach($this->observers as $observer) {
            $observer->updatePrice();
        }
    }
}

class Item {
    private $basePrice;
    private $price;

    public function __construct() {
        ExchangeRate::getInstance()->newObserver($this);
        $this->updatePrice();
    }

    public function updatePrice() {
        $this->price = $this->basePrice * ExchangeRate::getInstance()->getRate();
    }
}

...

$item1 = new Item();
$item2 = new Item();

ExchangeRate::getInstance()->updateRate(65.41);

5. Строитель (Builder)

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

class Coffee {
    private $coffee;
    private $milk;
    private $milkFoam;
}

abstract BuilderCoffee {
    private $object;

    public function createCoffee() {
        $this->object = new Coffee();
    }
    public function getCoffee() {
        return $this->object;
    }

    abstract public function buildCoffee();
    abstract public function buildMilk();
    abstract public function buildMilkFoam();
}

class BuilderCoffeeEspresso extends BuilderCoffee {
    public function buildCoffee() {
        $this->object->setCoffee('normal');
    }
    public function buildMilk() {
        $this->object->setMilk('none');
    }
    public function buildMilkFoam() {
        $this->object->setMilkFoam('none');
    }
}

class BuilderCoffeeCappuccino extends BuilderCoffee {
    public function buildCoffee() {
        $this->object->setCoffee('normal');
    }
    public function buildMilk() {
        $this->object->setMilk('normal');
    }
    public function buildMilkFoam() {
        $this->object->setMilk('normal');
    }
}

class BuilderCoffeeLatte extends BuilderCoffee {
    public function buildCoffee() {
        $this->object->setCoffee('normal');
    }
    public function buildMilk() {
        $this->object->setMilk('high');
    }
    public function buildMilkFoam() {
        $this->object->setMilkFoam('normal');
    }
}

class CoffeeMaker {
    private $builder;

    public function setBuilder($builder) { ... }

    public function make() {
        $this->builder->createCoffee();
        $this->builder->buildCoffee();
        $this->builder->buildMilk();
        $this->builder->buildMilkFoam();
    }

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

...

$maker = new CoffeeMaker();
$maker->setBuilder(new BuilderCoffeeCappuccino());
$maker->make();
$coffee = $maker->getCoffee();

6. Адаптер (Adapter)

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

class SampleClass {
    public function firstNameMethod($a, $b) {
        return $a + $b;
    }
}

class AnotherClass {
    public function secondNameMethod($a, $b) {
        return $a + $b;
    }
}

class SampleAdapter extends SampleClass {
    public function sum($a, $b) {
        return $this->firstNameMethod($a, $b);
    }
}

class AnotherAdapter extends AnotherClass {
    public function sum($a, $b) {
        return $this->secondNameMethod($a, $b);
    }
}

class Result {
    public static function getSum($adapterClass, $a, $b) {
        $adapter = new $adapterClass();
        return $adapter->sum($a, $b);
    }
}

...

$result1 = Result::getSum('sampleAdapter', 2, 1);
$result2 = Result::getSum('anotherAdapter', 1, 2);

7. Состояние (State)

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

class Context {
    private $state;

    public function getValue() {
        $this->state->handleRequest();
    }

    public function setState($state) {
        $this->state = $state;
    }
}

abstract class State {
    public function handleRequest();
}

class StateGreen extends State {
    public function handleRequest() {
        return "green";
    }
}

class StateYellow extends State {
    public function handleRequest() {
        return "yellow";
    }
}

class StateBlue extends State {
    public function handleRequest() {
        return "blue";
    }
}

$characterColor = new Context();

$characterColor->setState(new StateYellow());
echo($characterColor->getValue()); // yellow

$characterColor->setState(new StateBlue());
echo($characterColor->getValue()); // blue