33 вопросов
Порождающие (Creational) - создание объектов: Singleton, Factory, Abstract Factory, Builder, Prototype. Решают проблему инстанцирования.
Структурные (Structural) - композиция классов и объектов: Adapter, Decorator, Facade, Bridge, Composite, Proxy, Flyweight. Упрощают связи между сущностями.
Поведенческие (Behavioral) - распределение ответственности и взаимодействие: Observer, Strategy, Command, State, Template Method, Chain of Responsibility, Mediator, Visitor, Iterator, Memento. Описывают алгоритмы и потоки данных.
Паттерн гарантирует единственный экземпляр класса в приложении.
class Config {
private static ?self $instance = null;
private function __construct() {}
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}Применять для: глобального конфига, логгера, подключения к БД. Минусы: усложняет тестирование (сложно подменить), скрытая глобальная зависимость. В PHP часто предпочтительнее DI-контейнер.
Factory Method - метод класса, создающий объекты (делегирует создание подклассам).
interface LoggerFactory {
public function createLogger(): Logger;
}
class FileLoggerFactory implements LoggerFactory {
public function createLogger(): Logger { return new FileLogger(); }
}Abstract Factory - фабрика фабрик: создает семейства связанных объектов (например, UI-виджеты для Windows и Mac). Один интерфейс, несколько реализаций для разных "семейств".
Пошаговое создание сложного объекта через отдельный класс Builder. Удобно при многих опциональных параметрах.
$query = (new QueryBuilder())
->select(['id', 'name'])
->from('users')
->where('active', true)
->orderBy('name')
->limit(10)
->build();Избегает "телескопических" конструкторов с десятками параметров. Director (опционально) задает порядок шагов.
Создание новых объектов через клонирование существующего (прототипа), а не через конструктор.
class Document implements Cloneable {
public function __clone() {
$this->sections = array_map(fn($s) => clone $s, $this->sections);
}
}
$doc2 = clone $doc1;Полезно когда создание объекта дорого (сложная инициализация, запросы к БД) или когда нужно копировать с небольшими изменениями.
Структурный паттерн: преобразует интерфейс одного класса в другой, ожидаемый клиентом. Позволяет использовать несовместимые интерфейсы вместе.
class StripeAdapter implements PaymentGateway {
public function __construct(private StripeClient $stripe) {}
public function charge(float $amount, string $token): void {
$this->stripe->charges->create(['amount' => $amount * 100, 'source' => $token]);
}
}Объектный адаптер делегирует вызовы обернутому объекту; классовый - наследует целевой класс (в PHP реже).
Динамически добавляет объекту новое поведение, оборачивая его в объект-декоратор с тем же интерфейсом.
$handler = new LoggingDecorator(
new CacheDecorator(
new DatabaseHandler()
)
);Альтернатива наследованию для расширения функциональности. В PHP часто через обертки над PSR-интерфейсами (Logger, Cache, HttpClient).
Упрощенный единый интерфейс к подсистеме (набору классов). Скрывает сложность за простым API.
class OrderFacade {
public function __construct(
private OrderRepository $repo,
private PaymentService $payment,
private Notifier $notifier
) {}
public function placeOrder(Cart $cart): Order {
$order = $this->repo->create($cart);
$this->payment->charge($order);
$this->notifier->send($order);
return $order;
}
}Клиент вызывает один метод вместо координации нескольких сервисов.
Разделяет абстракцию и реализацию так, чтобы они могли меняться независимо. Вместо множества подклассов (Shape+Color комбинации) - две иерархии: абстракция и реализация.
interface Renderer { public function renderCircle(float $r): string; }
class VectorRenderer implements Renderer { ... }
class RasterRenderer implements Renderer { ... }
abstract class Shape {
public function __construct(protected Renderer $renderer) {}
}
class Circle extends Shape { ... }Новые формы и новые способы отрисовки добавляются без взрывного роста классов.
Древовидная структура: клиент работает с листьями и составными узлами единообразно через общий интерфейс.
interface Component { public function render(): string; }
class Leaf implements Component { public function render(): string { return "Leaf"; } }
class Composite implements Component {
private array $children = [];
public function add(Component $c): void { $this->children[] = $c; }
public function render(): string {
return implode('', array_map(fn($c) => $c->render(), $this->children));
}
}Примеры: дерево DOM, меню с подменю, файловая система.
Объект-заместитель контролирует доступ к другому объекту: ленивая инициализация, кеширование, проверка прав, логирование.
class LazyRepositoryProxy implements UserRepositoryInterface {
private ?UserRepository $real = null;
public function find(int $id): User {
$this->real ??= new UserRepository($this->connection);
return $this->real->find($id);
}
}Виды: Virtual (ленивая загрузка), Protection (access control), Remote, Cache proxy.
Разделение общего состояния между множеством объектов для экономии памяти. Общее состояние выносится в один объект (flyweight), уникальное хранится снаружи.
class TreeType {
public function __construct(private string $name, private string $color) {}
}
class TreeFactory {
private array $types = [];
public function getType(string $name, string $color): TreeType {
$key = $name . '-' . $color;
return $this->types[$key] ??= new TreeType($name, $color);
}
}Применение: символы в текстовом редакторе, деревья в игре, повторяющиеся иконки.
Паттерн подписки: объект (Subject) хранит список наблюдателей и уведомляет их об изменениях.
interface Observer { public function update(Subject $subject): void; }
class Subject {
private array $observers = [];
public function attach(Observer $o): void { $this->observers[] = $o; }
public function notify(): void {
foreach ($this->observers as $o) { $o->update($this); }
}
}В PHP: Symfony EventDispatcher, Laravel Events. Слабая связь между издателем и подписчиками.
Семейство взаимозаменяемых алгоритмов, инкапсулированных в отдельные классы. Выбор стратегии в рантайме.
interface SortStrategy { public function sort(array $data): array; }
class QuickSort implements SortStrategy { ... }
class Context {
public function __construct(private SortStrategy $strategy) {}
public function setStrategy(SortStrategy $s): void { $this->strategy = $s; }
public function execute(array $data): array { return $this->strategy->sort($data); }
}Избегает условных веток по типу алгоритма. Часто через внедрение стратегии в конструктор (DI).
Инкапсулирует запрос как объект: действие + параметры. Позволяет ставить операции в очередь, отменять, логировать.
interface Command { public function execute(): void; }
class CreateOrderCommand implements Command {
public function __construct(private OrderService $service, private OrderDTO $dto) {}
public function execute(): void { $this->service->create($this->dto); }
}
$bus->dispatch(new CreateOrderCommand($service, $dto));Используется в очередях (Laravel Jobs, Symfony Messenger), Undo/Redo, макросах.
Поведение объекта зависит от внутреннего состояния; объект меняет класс поведения при смене состояния.
interface State { public function proceed(Order $order): void; }
class DraftState implements State { public function proceed(Order $o): void { $o->setState(new ConfirmedState()); } }
class Order {
private State $state;
public function proceed(): void { $this->state->proceed($this); }
}Избегает больших switch/if по состоянию. Переходы инкапсулированы в классах состояний.
Базовый класс задает скелет алгоритма (шаги), подклассы переопределяют отдельные шаги.
abstract class DataImporter {
final public function import(string $path): void {
$data = $this->read($path);
$normalized = $this->normalize($data);
$this->persist($normalized);
}
abstract protected function read(string $path): array;
abstract protected function normalize(array $data): array;
protected function persist(array $data): void { /* default */ }
}Переиспользование общей логики без дублирования. Хук-методы - опциональные переопределяемые шаги.
Запрос передается по цепочке обработчиков; каждый решает, обработать самому или передать дальше.
interface Handler {
public function setNext(Handler $h): Handler;
public function handle(Request $request): ?Response;
}
class AuthMiddleware implements Handler {
private ?Handler $next = null;
public function setNext(Handler $h): Handler { $this->next = $h; return $h; }
public function handle(Request $r): ?Response {
if (!$this->auth->check($r)) return $this->unauthorized();
return $this->next?->handle($r);
}
}Примеры: middleware, валидация по цепочке, логирование/кэш по цепочке.
Объект-посредник инкапсулирует взаимодействие множества объектов. Компоненты не ссылаются друг на друга, только на медиатор.
class ChatRoom {
public function send(User $from, string $msg): void {
foreach ($this->users as $user) {
if ($user !== $from) $user->receive($from, $msg);
}
}
}Снижает связанность между коллегами. Минус: медиатор может разрастись. Примеры: диалоги в UI, оркестрация сервисов.
Добавляет новые операции к иерархии классов без изменения самих классов. Двойная диспетчеризация: элемент принимает посетителя, посетитель вызывает метод для конкретного типа элемента.
interface Visitor { function visitA(ElementA $e): void; function visitB(ElementB $e): void; }
interface Element { function accept(Visitor $v): void; }
class ElementA implements Element {
public function accept(Visitor $v): void { $v->visitA($this); }
}Применение: экспорт в разные форматы (JSON, XML), подсчет статистики по AST. Минус: добавление нового типа элемента требует менять всех посетителей.
Доступ к элементам коллекции последовательно без раскрытия внутренней структуры.
class Collection implements IteratorAggregate {
public function getIterator(): Iterator {
return new ArrayIterator($this->items);
}
}
foreach ($collection as $item) { ... }В PHP: Iterator, IteratorAggregate, Generator (yield). Позволяет ленивую итерацию и единый способ обхода разных структур.
Сохранение и восстановление внутреннего состояния объекта без нарушения инкапсуляции. Memento - объект-снимок состояния; Originator создает и восстанавливается из снимка; Caretaker хранит снимки.
class Memento { public function __construct(private string $state) {}
public function getState(): string { return $this->state; }
}
$originator->save(); // создает Memento
$caretaker->add($originator->saveToMemento());
$originator->restore($caretaker->get(0));Используется для Undo, снапшотов, отката конфигурации.
Инкапсулирует бизнес-правило в объект с методом isSatisfiedBy(candidate). Спецификации можно комбинировать (and, or, not).
interface Specification { public function isSatisfiedBy($candidate): bool; }
class ActiveUserSpec implements Specification {
public function isSatisfiedBy($user): bool { return $user->isActive(); }
}
$spec = new ActiveUserSpec()->and(new PremiumSpec());Удобно для фильтрации репозиториев, валидации доменных правил, переиспользуемых условий.
Стиль API, при котором вызовы методов можно цепочкой (каждый метод возвращает объект для следующего вызова).
$query->select('id', 'name')
->from('users')
->where('active', 1)
->orderBy('name')
->limit(10);Улучшает читаемость. Реализация: методы возвращают $this или новый билдер. Не путать с паттерном Builder - fluent может быть частью любого API.
Обобщение Singleton: ограниченное множество именованных экземпляров (по ключу).
class Multiton {
private static array $instances = [];
public static function getInstance(string $key): self {
if (!isset(self::$instances[$key])) {
self::$instances[$key] = new self($key);
}
return self::$instances[$key];
}
}Пример: соединения к разным БД по имени. В современном PHP чаще решают через DI и именованные сервисы.
Абстракция доступа к данным: коллекция доменных объектов в памяти. Клиент работает с сущностями, не зная о SQL/хранилище.
interface UserRepositoryInterface {
public function find(int $id): ?User;
public function save(User $user): void;
public function findByEmail(string $email): ?User;
}
class EloquentUserRepository implements UserRepositoryInterface {
public function find(int $id): ?User { return User::find($id); }
// ...
}Плюсы: тестируемость (подмена на in-memory), смена хранилища, единая точка доступа к данным.
Глобальный реестр, по запросу возвращающий сервисы. Клиент запрашивает зависимость по имени/типу.
$logger = $locator->get(LoggerInterface::class);
$db = $locator->get('db');Минусы: скрытые зависимости, сложнее тесты, код привязан к локатору. Предпочтительнее Dependency Injection: зависимости передаются явно (конструктор/сеттер).
Принцип: управление потоком и созданием зависимостей передается внешней среде (фреймворку, контейнеру), а не самому коду.
Вместо "класс сам создает зависимости" - "зависимости передаются извне". Конкретизация IoC - DI (Dependency Injection): внедрение зависимостей через конструктор, метод или свойство.
Плюсы: слабая связанность, тестируемость, гибкая замена реализаций.
Контейнер знает, как создавать объекты и разрешать их зависимости (по типам, именам, тегам). При запросе сервиса контейнер создает его и все зависимости рекурсивно.
// Регистрация
$container->bind(LoggerInterface::class, FileLogger::class);
$container->singleton(Connection::class, fn() => new PdoConnection(...));
// Резолв
$logger = $container->make(LoggerInterface::class);В PHP: Laravel Container, Symfony DI, PHP-DI. Поддерживают autowiring, теги, фабрики.
Model - данные и бизнес-логика (домен, репозитории, сервисы). View - представление (шаблоны, отображение). Controller - принимает запрос, вызывает модель, выбирает view и отдает ответ.
Контроллер не должен содержать бизнес-логику - только оркестрацию. Толстая модель, тонкий контроллер. В вебе: Front Controller (один entry point) + роутинг к конкретному контроллеру.
Controller - HTTP: валидация ввода, вызов сервиса, формирование ответа (JSON/redirect). Без бизнес-логики.
Service - слой приложения: оркестрация репозиториев и домена, транзакции, события. Содержит use-case логику.
Repository - доступ к данным: find, save, выборки. Абстракция над БД/API. Возвращает сущности или коллекции.
Запрос: Controller -> Service -> Repository -> DB. Модель (Entity) - доменный объект; репозиторий работает с моделями.
Active Record: объект инкапсулирует и данные, и персистентность. Методы save(), delete(), статические find(). Пример: Laravel Eloquent. Простота, быстрый старт; минус - смешение домена и доступа к БД.
Data Mapper: сущности "глупые", маппер отдельно переносит данные в БД и обратно. Пример: Doctrine. Чистый домен, тестируемость; минус - больше кода и сложность.