EasyТеория4 min

Обзор паттернов проектирования

Зачем нужны паттерны, SOLID принципы, классификация GoF, антипаттерны

Зачем нужны паттерны?

Паттерны проектирования (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)

Вопросы с экзамена ZCE

Проверь себя

5 из 6

Какой Symfony-компонент реализует паттерн Observer?

К какой группе GoF паттернов относится Strategy?

ArrayAccess является примером:

Что такое антипаттерн God Object?

Какой принцип SOLID гласит, что класс должен иметь только одну причину для изменения?