Применение шаблонов проектирования в 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(); // Документ опубликован.
?>
Результат: Документ отправлен на модерацию. Документ возвращён в черновик. Документ отправлен на модерацию. Документ опубликован.