Паттерны проектирования в PHP: от основ до продвинутых техник
Паттерны проектирования (design patterns) — это многократно проверенные решения типовых задач, возникающих при разработке программного обеспечения. Для PHP-разработчика знание паттернов — это не просто галочка в резюме, а реальный инструмент для создания гибкого, поддерживаемого и масштабируемого кода. В этой статье мы разберем три ключевые группы паттернов GoF (банды четырех) с конкретными примерами на PHP, которые вы сможете сразу применить в своих проектах.
1. Порождающие паттерны (Creational Patterns)
Эти паттерны отвечают за создание объектов, скрывая логику инстанцирования и делая систему независимой от способа создания и композиции объектов.
Singleton (Одиночка)
Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. Классический пример — подключение к базе данных.
<?php
class Database {
private static ?Database $instance = null;
private PDO $pdo;
private function __construct() {
$this->pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
}
public static function getInstance(): Database {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function query(string $sql): array {
// Логика выполнения запроса
return $this->pdo->query($sql)->fetchAll();
}
// Запрещаем клонирование и десериализацию
private function __clone() {}
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
}
// Использование
$db = Database::getInstance();
$results = $db->query("SELECT * FROM users");
?>
Factory Method (Фабричный метод)
Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта. Отлично подходит для создания семейств связанных объектов.
<?php
interface Logger {
public function log(string $message): void;
}
class FileLogger implements Logger {
public function log(string $message): void {
file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND);
}
}
class DatabaseLogger implements Logger {
public function log(string $message): void {
// Сохраняем в БД
echo "Logged to DB: $message\n";
}
}
abstract class LoggerFactory {
abstract public function createLogger(): Logger;
public function logMessage(string $message): void {
$logger = $this->createLogger();
$logger->log($message);
}
}
class FileLoggerFactory extends LoggerFactory {
public function createLogger(): Logger {
return new FileLogger();
}
}
class DatabaseLoggerFactory extends LoggerFactory {
public function createLogger(): Logger {
return new DatabaseLogger();
}
}
// Использование
$factory = new FileLoggerFactory();
$factory->logMessage("Это сообщение будет записано в файл");
?>
2. Структурные паттерны (Structural Patterns)
Они определяют, как классы и объекты могут быть объединены в более крупные структуры, сохраняя при этом гибкость и эффективность.
Adapter (Адаптер)
Позволяет объектам с несовместимыми интерфейсами работать вместе. Адаптер выступает в роли обертки, преобразующей вызовы одного интерфейса в другой.
<?php
// Старый класс с неудобным интерфейсом
class OldPaymentSystem {
public function processPayment(float $amount, string $currency): string {
return "Processing $amount $currency via legacy system";
}
}
// Новый интерфейс, который мы хотим использовать
interface NewPaymentInterface {
public function pay(float $amount): string;
}
// Адаптер
class PaymentAdapter implements NewPaymentInterface {
private OldPaymentSystem $oldSystem;
public function __construct(OldPaymentSystem $oldSystem) {
$this->oldSystem = $oldSystem;
}
public function pay(float $amount): string {
// Преобразуем вызов: новый интерфейс → старый метод
return $this->oldSystem->processPayment($amount, 'USD');
}
}
// Использование
$oldSystem = new OldPaymentSystem();
$adapter = new PaymentAdapter($oldSystem);
echo $adapter->pay(100.50); // Processing 100.5 USD via legacy system
?>
Decorator (Декоратор)
Динамически добавляет объекту новые обязанности, не изменяя его структуру. Является альтернативой наследованию.
<?php
interface Coffee {
public function cost(): float;
public function description(): string;
}
class SimpleCoffee implements Coffee {
public function cost(): float {
return 10.0;
}
public function description(): string {
return "Simple coffee";
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public function cost(): float {
return $this->coffee->cost() + 2.0;
}
public function description(): string {
return $this->coffee->description() . ", with milk";
}
}
class SugarDecorator extends CoffeeDecorator {
public function cost(): float {
return $this->coffee->cost() + 0.5;
}
public function description(): string {
return $this->coffee->description() . ", with sugar";
}
}
// Использование
$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);
echo $coffee->description() . " costs: " . $coffee->cost();
// Simple coffee, with milk, with sugar costs: 12.5
?>
3. Поведенческие паттерны (Behavioral Patterns)
Определяют алгоритмы и способы взаимодействия между объектами, распределяя обязанности между ними.
Strategy (Стратегия)
Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Позволяет изменять алгоритм независимо от клиентов, которые его используют.
<?php
interface SortStrategy {
public function sort(array $data): array;
}
class BubbleSortStrategy implements SortStrategy {
public function sort(array $data): array {
echo "Using Bubble Sort\n";
// Реализация пузырьковой сортировки
$n = count($data);
for ($i = 0; $i < $n - 1; $i++) {
for ($j = 0; $j < $n - $i - 1; $j++) {
if ($data[$j] > $data[$j + 1]) {
$temp = $data[$j];
$