Применение шаблонов проектирования в PHP для работы с объектами

Раздел: Программирование на PHP -> Паттерны проектирования

Обзор шаблонов проектирования объектов в PHP

Шаблоны проектирования (паттерны) – это типовые решения повторяющихся задач при построении объектно-ориентированных приложений. В PHP они помогают организовать код гибко и переиспользуемо. Рассмотрим несколько популярных паттернов на примерах.

Паттерн «Фабрика» (Factory Method)

Как создавать объекты без жёсткой привязки к их конкретным классам?

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

<?php
interface Logger {
    public function log(string $message): void;
}

class FileLogger implements Logger {
    public function log(string $message): void {
        file_put_contents('log.txt', $message . PHP_EOL, FILE_APPEND);
    }
}

class NullLogger implements Logger {
    public function log(string $message): void {
        // ничего не делаем
    }
}

class LoggerFactory {
    public static function create(string $type): Logger {
        return match($type) {
            'file' => new FileLogger(),
            'null' => new NullLogger(),
            default => throw new InvalidArgumentException('Unknown logger type'),
        };
    }
}

// Использование
$logger = LoggerFactory::create('file');
$logger->log('Тестовое сообщение');
?>

Php шаблоны и методики программирования (объекты и шаблоны в php)

Цель: отделить процесс создания объектов от их использования. Упрощает добавление новых типов продуктов.

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

Типичные ошибки:

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

Паттерн «Одиночка» (Singleton)

Как гарантировать, что класс имеет только один экземпляр?

Singleton ограничивает инстанцирование класса одним объектом. В PHP это достигается скрытым конструктором и статическим методом получения экземпляра.

<?php
class Database {
    private static ?Database $instance = null;
    private PDO $pdo;

    private function __construct() {
        $this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    }

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

    public function getConnection(): PDO {
        return $this->pdo;
    }

    // Запрещаем клонирование и сериализацию
    private function __clone() {}
    public function __wakeup() {
        throw new \Exception('Cannot unserialize singleton');
    }
}

$db = Database::getInstance();
?>

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

Случаи использования: логгеры, кэш, пулы соединений, реестры.

Проблемы:

  • Затрудняет юнит-тестирование (глобальное состояние).
  • Нарушает принцип единственной ответственности (управление жизненным циклом).
  • Сложность при параллельных запросах (в PHP не актуально, но в CLI скриптах возможно).

Паттерн «Стратегия» (Strategy)

Как изменить поведение объекта во время выполнения без изменения его кода?

Стратегия позволяет взаимозаменять алгоритмы, выделяя их в отдельные классы. Основной объект использует интерфейс стратегии.

<?php
interface SortStrategy {
    public function sort(array $data): array;
}

class BubbleSort implements SortStrategy {
    public function sort(array $data): array {
        // реализация пузырьковой сортировки
        $n = count($data);
        for ($i = 0; $i < $n-1; $i++) {
            for ($j = 0; $j < $n-$i-1; $j++) {
                if ($data[$j] > $data[$j+1]) {
                    [$data[$j], $data[$j+1]] = [$data[$j+1], $data[$j]];
                }
            }
        }
        return $data;
    }
}

class QuickSort implements SortStrategy {
    public function sort(array $data): array {
        if (count($data) < 2) return $data;
        $pivot = $data[0];
        $left = $right = [];
        for ($i = 1; $i < count($data); $i++) {
            if ($data[$i] < $pivot) {
                $left[] = $data[$i];
            } else {
                $right[] = $data[$i];
            }
        }
        return array_merge($this->sort($left), [$pivot], $this->sort($right));
    }
}

class Sorter {
    private SortStrategy $strategy;

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

    public function setStrategy(SortStrategy $strategy): void {
        $this->strategy = $strategy;
    }

    public function sort(array $data): array {
        return $this->strategy->sort($data);
    }
}

// Использование
$sorter = new Sorter(new BubbleSort());
echo implode(', ', $sorter->sort([3,1,2])); // 1,2,3
$sorter->setStrategy(new QuickSort());
echo implode(', ', $sorter->sort([3,1,2])); // 1,2,3
?>

Цель: выделить алгоритмы в отдельные классы, чтобы легко заменять их и добавлять новые.

Случаи использования: разные способы валидации, форматирования, оплаты, сортировки.

Ошибки:

  • Слишком много стратегий – усложнение кода.
  • Выбор стратегии через if-else вместо фабрики.
  • Нарушение принципа открытости/закрытости при неправильной архитектуре.

Паттерн «Наблюдатель» (Observer)

Как организовать механизм оповещения множества объектов об изменениях состояния другого объекта?

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

<?php
interface Observer {
    public function update(Subject $subject): void;
}

class Subject {
    private array $observers = [];
    private int $state;

    public function attach(Observer $observer): void {
        $this->observers[] = $observer;
    }

    public function detach(Observer $observer): void {
        $this->observers = array_filter($this->observers, fn($o) => $o !== $observer);
    }

    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function setState(int $state): void {
        $this->state = $state;
        $this->notify();
    }

    public function getState(): int {
        return $this->state;
    }
}

class ConcreteObserver implements Observer {
    private string $name;

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

    public function update(Subject $subject): void {
        echo "Наблюдатель {$this->name} получил новое состояние: {$subject->getState()}\n";
    }
}

$subject = new Subject();
$observer1 = new ConcreteObserver('A');
$observer2 = new ConcreteObserver('B');
$subject->attach($observer1);
$subject->attach($observer2);
$subject->setState(5);
// Вывод: Наблюдатель A получил новое состояние: 5
// Наблюдатель B получил новое состояние: 5
?>

Цель: уменьшить связанность, позволяя одним объектам реагировать на изменения других.

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

Проблемы:

  • Утечка памяти, если наблюдатели не отписываются (в PHP сборка мусора решает, но ссылки могут оставаться).
  • Порядок уведомления наблюдателей может быть непредсказуемым.
  • Чрезмерное использование приводит к сложноотлаживаемому потоку данных.

Расширенные примеры применения паттернов в PHP

Паттерн «Декоратор» (Decorator)

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

Пример
<?php
interface Text {
    public function render(): string;
}

class PlainText implements Text {
    private string $text;

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

    public function render(): string {
        return $this->text;
    }
}

abstract class TextDecorator implements Text {
    protected Text $text;

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

class BoldDecorator extends TextDecorator {
    public function render(): string {
        return "<b>" . $this->text->render() . "</b>";
    }
}

class ItalicDecorator extends TextDecorator {
    public function render(): string {
        return "<i>" . $this->text->render() . "</i>";
    }
}

// Использование
$text = new PlainText('Hello World');
$boldItalic = new BoldDecorator(new ItalicDecorator($text));
echo $boldItalic->render(); // <b><i>Hello World</i></b>
?>
Результат: <b><i>Hello World</i></b>

Паттерн «Компоновщик» (Composite)

Позволяет работать с одиночными и составными объектами единообразно. Пример: графические элементы.

Пример
<?php
interface Graphic {
    public function draw(): string;
}

class Circle implements Graphic {
    public function draw(): string {
        return 'Круг';
    }
}

class Square implements Graphic {
    public function draw(): string {
        return 'Квадрат';
    }
}

class CompositeGraphic implements Graphic {
    private array $children = [];

    public function add(Graphic $graphic): void {
        $this->children[] = $graphic;
    }

    public function draw(): string {
        $result = [];
        foreach ($this->children as $child) {
            $result[] = $child->draw();
        }
        return 'Группа: [' . implode(', ', $result) . ']';
    }
}

$group1 = new CompositeGraphic();
$group1->add(new Circle());
$group1->add(new Square());

$group2 = new CompositeGraphic();
$group2->add(new Circle());
$group2->add($group1);

echo $group2->draw(); // Группа: [Круг, Группа: [Круг, Квадрат]]
?>
Результат: Группа: [Круг, Группа: [Круг, Квадрат]]

Паттерн «Цепочка обязанностей» (Chain of Responsibility)

Позволяет передавать запрос последовательно по цепочке обработчиков. Пример: middleware в PHP.

Пример
<?php
interface Handler {
    public function setNext(Handler $handler): Handler;
    public function handle(string $request): ?string;
}

abstract class AbstractHandler implements Handler {
    private ?Handler $next = null;

    public function setNext(Handler $handler): Handler {
        $this->next = $handler;
        return $handler;
    }

    public function handle(string $request): ?string {
        if ($this->next) {
            return $this->next->handle($request);
        }
        return null;
    }
}

class AuthenticationHandler extends AbstractHandler {
    public function handle(string $request): ?string {
        if ($request === 'authenticated') {
            echo "Authentication passed.\n";
            return parent::handle($request);
        }
        return "Authentication failed.";
    }
}

class AuthorizationHandler extends AbstractHandler {
    public function handle(string $request): ?string {
        if ($request === 'authorized') {
            echo "Authorization passed.\n";
            return parent::handle($request);
        }
        return "Authorization failed.";
    }
}

$auth = new AuthenticationHandler();
$authz = new AuthorizationHandler();
$auth->setNext($authz);

echo $auth->handle('authenticated'); // Authentication passed. Затем Authorization failed. (т.к. не authorized)
?>
Результат:
Authentication passed.
Authorization failed.

Паттерн «Состояние» (State)

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

Пример
<?php
interface DocumentState {
    public function publish(Document $document): void;
    public function reject(Document $document): void;
}

class DraftState implements DocumentState {
    public function publish(Document $document): void {
        $document->setState(new ModerateState());
        echo "Документ отправлен на модерацию.\n";
    }

    public function reject(Document $document): void {
        echo "Черновик не может быть отклонён.\n";
    }
}

class ModerateState implements DocumentState {
    public function publish(Document $document): void {
        $document->setState(new PublishedState());
        echo "Документ опубликован.\n";
    }

    public function reject(Document $document): void {
        $document->setState(new DraftState());
        echo "Документ возвращён в черновик.\n";
    }
}

class PublishedState implements DocumentState {
    public function publish(Document $document): void {
        echo "Документ уже опубликован.\n";
    }

    public function reject(Document $document): void {
        echo "Нельзя отклонить опубликованный документ.\n";
    }
}

class Document {
    private DocumentState $state;

    public function __construct() {
        $this->state = new DraftState();
    }

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

    public function publish(): void {
        $this->state->publish($this);
    }

    public function reject(): void {
        $this->state->reject($this);
    }
}

$doc = new Document();
$doc->publish(); // Документ отправлен на модерацию.
$doc->reject();  // Документ возвращён в черновик.
$doc->publish(); // Документ отправлен на модерацию.
$doc->publish(); // Документ опубликован.
?>
Результат:
Документ отправлен на модерацию.
Документ возвращён в черновик.
Документ отправлен на модерацию.
Документ опубликован.

Объекты и шаблоны в PHP - comments

En
Php шаблоны и методики программирования (php)