Зачем нужны паттерны?
Паттерны проектирования (Design Patterns) -- это проверенные решения типичных задач в объектно-ориентированном проектировании. Они были систематизированы в 1994 году в книге "Gang of Four" (GoF) -- Gamma, Helm, Johnson, Vlissides.
Паттерн -- это не готовый код, а шаблон решения. Как архитектурный чертёж: его нужно адаптировать под конкретную задачу.
Что даёт знание паттернов?
- Общий язык -- "Здесь используем Strategy" понятнее, чем "Сделаем набор классов с общим интерфейсом и будем переключать"
- Проверенные решения -- не изобретать велосипед для типичных задач
- Гибкость -- код легче расширять и модифицировать
- Понимание фреймворков -- Symfony и Laravel построены на паттернах
SOLID принципы
Паттерны строятся на фундаменте SOLID. Кратко о каждом принципе:
S -- Single Responsibility Principle (Единственная ответственность)
<?php
declare(strict_types=1);
// Bad: class does everything
final class UserManager
{
public function createUser(string $email): void { /* ... */ }
public function sendEmail(string $to): void { /* ... */ }
public function generateReport(): string { return ''; }
}
// Good: each class has one responsibility
final class UserService
{
public function createUser(string $email): void { /* ... */ }
}
final class EmailSender
{
public function send(string $to, string $subject): void { /* ... */ }
}
final class UserReportGenerator
{
public function generate(): string { return ''; }
}
O -- Open/Closed Principle (Открыт для расширения, закрыт для изменения)
<?php
declare(strict_types=1);
// Good: open for extension via interface
interface DiscountCalculator
{
public function calculate(float $price): float;
}
final class RegularDiscount implements DiscountCalculator
{
public function calculate(float $price): float
{
return $price * 0.05;
}
}
final class VipDiscount implements DiscountCalculator
{
public function calculate(float $price): float
{
return $price * 0.20;
}
}
// Adding new discount type requires NO changes to existing code
final class BlackFridayDiscount implements DiscountCalculator
{
public function calculate(float $price): float
{
return $price * 0.50;
}
}
L -- Liskov Substitution Principle (Подстановка Лисков)
<?php
declare(strict_types=1);
// Interface defines contract
interface Shape
{
public function area(): float;
}
final readonly class Rectangle implements Shape
{
public function __construct(
private float $width,
private float $height,
) {}
public function area(): float
{
return $this->width * $this->height;
}
}
final readonly class Circle implements Shape
{
public function __construct(
private float $radius,
) {}
public function area(): float
{
return M_PI * $this->radius ** 2;
}
}
// Any Shape implementation can substitute another
function printArea(Shape $shape): void
{
echo $shape->area();
}
I -- Interface Segregation Principle (Разделение интерфейсов)
<?php
declare(strict_types=1);
// Bad: fat interface
interface Worker
{
public function work(): void;
public function eat(): void;
public function sleep(): void;
}
// Good: segregated interfaces
interface Workable
{
public function work(): void;
}
interface Feedable
{
public function eat(): void;
}
// Robot only implements what it needs
final class Robot implements Workable
{
public function work(): void { /* ... */ }
}
// Human implements both
final class HumanWorker implements Workable, Feedable
{
public function work(): void { /* ... */ }
public function eat(): void { /* ... */ }
}
D -- Dependency Inversion Principle (Инверсия зависимостей)
<?php
declare(strict_types=1);
// Abstraction
interface Logger
{
public function log(string $message): void;
}
// High-level module depends on abstraction
final readonly class OrderService
{
public function __construct(
private Logger $logger,
) {}
public function placeOrder(int $orderId): void
{
// Business logic...
$this->logger->log("Order #{$orderId} placed");
}
}
// Low-level implementations
final class FileLogger implements Logger
{
public function log(string $message): void
{
file_put_contents('/var/log/app.log', $message . "\n", FILE_APPEND);
}
}
final class DatabaseLogger implements Logger
{
public function log(string $message): void
{
// Save to database
}
}
Классификация GoF паттернов
23 паттерна GoF делятся на три группы:
Порождающие (Creational) -- создание объектов
| Паттерн | Задача |
|---|---|
| Singleton | Один экземпляр на всё приложение |
| Factory Method | Делегирование создания подклассам |
| Abstract Factory | Семейства связанных объектов |
| Builder | Пошаговое создание сложных объектов |
| Prototype | Клонирование объектов |
Структурные (Structural) -- композиция объектов
| Паттерн | Задача |
|---|---|
| Adapter | Совмещение несовместимых интерфейсов |
| Decorator | Динамическое добавление поведения |
| Facade | Упрощённый интерфейс к подсистеме |
| Proxy | Заместитель для контроля доступа |
| Composite | Древовидные структуры |
| Bridge | Разделение абстракции и реализации |
Поведенческие (Behavioral) -- взаимодействие объектов
| Паттерн | Задача |
|---|---|
| Strategy | Семейство алгоритмов, взаимозаменяемых на лету |
| Observer | Уведомление об изменениях |
| Command | Инкапсуляция запроса как объекта |
| Chain of Responsibility | Цепочка обработчиков |
| Template Method | Скелет алгоритма с переопределяемыми шагами |
| State | Изменение поведения при смене состояния |
| Iterator | Последовательный обход коллекции |
Как выбрать паттерн?
Нужно создать объект?
├── Один на всё приложение → Singleton
├── Сложный объект пошагово → Builder
├── Семейство объектов → Abstract Factory
├── Делегировать создание → Factory Method
└── Копия существующего → Prototype
Нужно изменить структуру?
├── Адаптировать интерфейс → Adapter
├── Добавить поведение → Decorator
├── Упростить доступ → Facade
├── Контролировать доступ → Proxy
└── Древовидная структура → Composite
Нужно изменить поведение?
├── Переключаемые алгоритмы → Strategy
├── Реакция на события → Observer
├── Отложенное выполнение → Command
├── Цепочка обработки → Chain of Responsibility
├── Шаблон с вариациями → Template Method
└── Поведение зависит от состояния → State
Антипаттерны -- чего избегать
God Object
<?php
declare(strict_types=1);
// Anti-pattern: one class rules them all
final class Application
{
public function handleRequest(): void { /* ... */ }
public function connectDatabase(): void { /* ... */ }
public function renderTemplate(): void { /* ... */ }
public function sendEmail(): void { /* ... */ }
public function processPayment(): void { /* ... */ }
public function generatePdf(): void { /* ... */ }
// 500+ methods...
}
Решение: Разбить на сервисы по ответственности (SRP).
Singleton Abuse
<?php
declare(strict_types=1);
// Anti-pattern: singleton everywhere
final class Config
{
private static ?self $instance = null;
public static function getInstance(): self
{
return self::$instance ??= new self();
}
private function __construct() {}
}
// Hidden dependency -- hard to test
final class UserService
{
public function getUser(): array
{
// Where does Config come from? Can't mock in tests!
$dbHost = Config::getInstance()->get('db.host');
return [];
}
}
Решение: Dependency Injection вместо прямого вызова Singleton.
Cargo Cult Programming
Использование паттерна без понимания, зачем он нужен:
<?php
declare(strict_types=1);
// Anti-pattern: unnecessary factory for one class
final class UserFactory
{
public function create(string $name): User
{
return new User($name); // Why not just "new User($name)"?
}
}
// Unnecessary abstraction
interface UserRepositoryInterface {}
final class UserRepository implements UserRepositoryInterface {}
// There is only ONE implementation and never will be another
Правило: Используйте паттерн, только когда есть реальная проблема, которую он решает.
Premature Abstraction
<?php
declare(strict_types=1);
// Anti-pattern: abstracting before there is need
interface StorageInterface
{
public function save(string $data): void;
}
interface StorageFactoryInterface
{
public function create(): StorageInterface;
}
abstract class AbstractStorage implements StorageInterface
{
abstract protected function doPersist(string $data): void;
}
// All this for a single file write?
final class FileStorage extends AbstractStorage
{
protected function doPersist(string $data): void
{
file_put_contents('data.txt', $data);
}
}
Правило: YAGNI -- "You Ain't Gonna Need It". Абстрагируйте только когда появляется вторая реализация.
Паттерны в Symfony
Symfony активно использует паттерны. Вот карта соответствий:
| Паттерн | Symfony-компонент |
|---|---|
| Strategy | Security Voters, Serializer Normalizers |
| Observer | EventDispatcher |
| Command | Console Commands, Messenger |
| Chain of Responsibility | Middleware, Security Firewalls |
| Factory | Form Factory, Service Factories |
| Decorator | Cache decorators, Logger decorators |
| Proxy | Doctrine Lazy Loading |
| Facade | Router, Mailer (simplified interfaces) |
| Builder | FormBuilder, QueryBuilder |
| Template Method | AbstractController |
| Iterator | Finder component |
| Composite | Form component (nested forms) |