← Назад к вопросам

С какими работал паттернами проектирования?

1.3 Junior🔥 161 комментариев
#Архитектура и паттерны#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Паттерны проектирования в моей практике

Я активно использую паттерны проектирования в своей работе. Расскажу о тех, которые применяю чаще всего и как они помогают улучшить код.

1. Repository Pattern

Это один из самых полезных паттернов, который я использую в каждом проекте. Он изолирует логику доступа к данным.

// Interface
interface UserRepositoryInterface {
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function save(User $user): void;
    public function delete(int $id): void;
}

// Implementation
class UserRepository implements UserRepositoryInterface {
    public function __construct(private PDO $pdo) {}
    
    public function findById(int $id): ?User {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        $data = $stmt->fetch(PDO::FETCH_ASSOC);
        return $data ? User::fromArray($data) : null;
    }
    
    public function save(User $user): void {
        $stmt = $this->pdo->prepare(
            'INSERT INTO users (name, email) VALUES (?, ?) ON DUPLICATE KEY UPDATE name = ?, email = ?'
        );
        $stmt->execute([$user->getName(), $user->getEmail(), $user->getName(), $user->getEmail()]);
    }
}

// Usage
class UserService {
    public function __construct(private UserRepositoryInterface $userRepository) {}
    
    public function getUserProfile(int $userId): UserDTO {
        $user = $this->userRepository->findById($userId);
        return UserDTO::fromUser($user);
    }
}

Преимущества:

  • Легко тестировать (мокируешь repository)
  • Можно менять реализацию (SQL, MongoDB, API) без изменения service
  • Единая точка доступа к данным

2. Service/Application Service Pattern

Это паттерн для организации бизнес-логики, выделяю её в отдельный слой.

class OrderService {
    public function __construct(
        private OrderRepositoryInterface $orderRepository,
        private PaymentGateway $paymentGateway,
        private EmailService $emailService,
        private InventoryService $inventoryService
    ) {}
    
    public function placeOrder(PlaceOrderRequest $request): Order {
        // Бизнес-логика
        
        // 1. Проверяем доступность товара
        foreach ($request->items as $item) {
            if (!$this->inventoryService->hasStock($item['product_id'], $item['quantity'])) {
                throw new OutOfStockException();
            }
        }
        
        // 2. Проводим платеж
        $paymentResult = $this->paymentGateway->charge(
            amount: $request->total,
            token: $request->paymentToken
        );
        
        if (!$paymentResult->isSuccessful()) {
            throw new PaymentFailedException();
        }
        
        // 3. Создаем заказ
        $order = Order::create([
            'user_id' => $request->userId,
            'total' => $request->total,
            'status' => 'confirmed',
            'payment_id' => $paymentResult->getId()
        ]);
        
        // 4. Зарезервируем товар
        foreach ($request->items as $item) {
            $this->inventoryService->reserve($item['product_id'], $item['quantity']);
        }
        
        // 5. Сохраняем заказ
        $this->orderRepository->save($order);
        
        // 6. Отправляем email
        $this->emailService->sendOrderConfirmation($order);
        
        return $order;
    }
}

// Usage в controller
class OrderController {
    public function __construct(private OrderService $orderService) {}
    
    public function store(Request $request) {
        try {
            $order = $this->orderService->placeOrder(
                PlaceOrderRequest::fromRequest($request)
            );
            return response()->json(['order_id' => $order->id], 201);
        } catch (PaymentFailedException $e) {
            return response()->json(['error' => 'Payment failed'], 422);
        }
    }
}

3. Strategy Pattern

Использую когда есть несколько вариантов алгоритма.

// Interface для стратегии
interface DiscountStrategyInterface {
    public function calculate(Order $order): float;
}

// Конкретные реализации
class PercentageDiscountStrategy implements DiscountStrategyInterface {
    public function __construct(private float $percentage) {}
    
    public function calculate(Order $order): float {
        return $order->getTotal() * ($this->percentage / 100);
    }
}

class FixedAmountDiscountStrategy implements DiscountStrategyInterface {
    public function __construct(private float $amount) {}
    
    public function calculate(Order $order): float {
        return min($this->amount, $order->getTotal());
    }
}

class VolumeBasedDiscountStrategy implements DiscountStrategyInterface {
    public function calculate(Order $order): float {
        $itemCount = $order->getItemCount();
        if ($itemCount >= 100) return $order->getTotal() * 0.15;
        if ($itemCount >= 50) return $order->getTotal() * 0.10;
        if ($itemCount >= 10) return $order->getTotal() * 0.05;
        return 0;
    }
}

// Context
class DiscountCalculator {
    private DiscountStrategyInterface $strategy;
    
    public function setStrategy(DiscountStrategyInterface $strategy): void {
        $this->strategy = $strategy;
    }
    
    public function calculate(Order $order): float {
        return $this->strategy->calculate($order);
    }
}

// Usage
$calculator = new DiscountCalculator();
$order = new Order();

// Применяем разные стратегии в зависимости от условия
if ($order->getCustomer()->isVIP()) {
    $calculator->setStrategy(new PercentageDiscountStrategy(20));
} elseif ($order->getItemCount() > 50) {
    $calculator->setStrategy(new VolumeBasedDiscountStrategy());
}

$discount = $calculator->calculate($order);

4. Observer Pattern

Использую для event-driven архитектуры.

// Event
class OrderPlacedEvent {
    public function __construct(
        public Order $order,
        public User $user
    ) {}
}

// Observers
interface EventListenerInterface {
    public function handle(OrderPlacedEvent $event): void;
}

class SendOrderConfirmationEmail implements EventListenerInterface {
    public function __construct(private EmailService $emailService) {}
    
    public function handle(OrderPlacedEvent $event): void {
        $this->emailService->sendOrderConfirmation($event->order, $event->user);
    }
}

class CreateOrderLog implements EventListenerInterface {
    public function __construct(private OrderLogRepository $logRepository) {}
    
    public function handle(OrderPlacedEvent $event): void {
        $this->logRepository->create([
            'order_id' => $event->order->id,
            'user_id' => $event->user->id,
            'action' => 'placed'
        ]);
    }
}

class UpdateUserStatistics implements EventListenerInterface {
    public function __construct(private UserStatsService $statsService) {}
    
    public function handle(OrderPlacedEvent $event): void {
        $this->statsService->incrementOrderCount($event->user->id);
        $this->statsService->addToTotalSpent($event->user->id, $event->order->total);
    }
}

class UpdateInventory implements EventListenerInterface {
    public function __construct(private InventoryService $inventoryService) {}
    
    public function handle(OrderPlacedEvent $event): void {
        foreach ($event->order->items as $item) {
            $this->inventoryService->reserve($item->product_id, $item->quantity);
        }
    }
}

// Event dispatcher
class EventDispatcher {
    private array $listeners = [];
    
    public function subscribe(string $eventClass, EventListenerInterface $listener): void {
        $this->listeners[$eventClass][] = $listener;
    }
    
    public function dispatch(object $event): void {
        $eventClass = get_class($event);
        foreach ($this->listeners[$eventClass] ?? [] as $listener) {
            $listener->handle($event);
        }
    }
}

// Usage в Laravel
EventDispatcher::subscribe(OrderPlacedEvent::class, new SendOrderConfirmationEmail($emailService));
EventDispatcher::subscribe(OrderPlacedEvent::class, new CreateOrderLog($logRepository));
EventDispatcher::subscribe(OrderPlacedEvent::class, new UpdateUserStatistics($statsService));
EventDispatcher::subscribe(OrderPlacedEvent::class, new UpdateInventory($inventoryService));

// В service
$order = $this->createOrder($data);
$dispatcher->dispatch(new OrderPlacedEvent($order, $user));

5. Factory Pattern

Использую для создания объектов с сложной логикой.

interface PaymentProcessorInterface {
    public function process(float $amount): PaymentResult;
}

class StripeProcessor implements PaymentProcessorInterface {
    public function __construct(private StripeClient $client) {}
    
    public function process(float $amount): PaymentResult {
        // Stripe логика
    }
}

class PayPalProcessor implements PaymentProcessorInterface {
    public function __construct(private PayPalClient $client) {}
    
    public function process(float $amount): PaymentResult {
        // PayPal логика
    }
}

// Factory
class PaymentProcessorFactory {
    public static function create(string $provider): PaymentProcessorInterface {
        return match($provider) {
            'stripe' => new StripeProcessor(new StripeClient(env('STRIPE_KEY'))),
            'paypal' => new PayPalProcessor(new PayPalClient(env('PAYPAL_KEY'))),
            default => throw new InvalidArgumentException("Unknown provider: $provider")
        };
    }
}

// Usage
$processor = PaymentProcessorFactory::create($order->payment_provider);
$result = $processor->process($order->total);

6. Decorator Pattern

Использую для добавления функциональности к существующим объектам.

interface OrderCalculatorInterface {
    public function calculate(Order $order): float;
}

class BaseOrderCalculator implements OrderCalculatorInterface {
    public function calculate(Order $order): float {
        return array_sum(
            array_map(fn($item) => $item->price * $item->quantity, $order->items)
        );
    }
}

// Decorators
class TaxDecorator implements OrderCalculatorInterface {
    public function __construct(
        private OrderCalculatorInterface $calculator,
        private float $taxRate = 0.1
    ) {}
    
    public function calculate(Order $order): float {
        $base = $this->calculator->calculate($order);
        return $base + ($base * $this->taxRate);
    }
}

class ShippingDecorator implements OrderCalculatorInterface {
    public function __construct(
        private OrderCalculatorInterface $calculator,
        private float $shippingCost = 10.0
    ) {}
    
    public function calculate(Order $order): float {
        return $this->calculator->calculate($order) + $this->shippingCost;
    }
}

class DiscountDecorator implements OrderCalculatorInterface {
    public function __construct(
        private OrderCalculatorInterface $calculator,
        private float $discountPercent = 0
    ) {}
    
    public function calculate(Order $order): float {
        $total = $this->calculator->calculate($order);
        return $total - ($total * $this->discountPercent / 100);
    }
}

// Usage
$calculator = new BaseOrderCalculator();
$calculator = new TaxDecorator($calculator);           // + налог
$calculator = new ShippingDecorator($calculator);      // + доставка
$calculator = new DiscountDecorator($calculator, 10);  // - 10% скидка

$total = $calculator->calculate($order);
// Расчет: (items) -> +tax -> +shipping -> -discount

7. Template Method Pattern

Использую для определения скелета алгоритма в базовом классе.

abstract class DataImporter {
    final public function import(string $filePath): ImportResult {
        // Template method - определяет порядок шагов
        $data = $this->readFile($filePath);
        $this->validate($data);
        $this->transform($data);
        $this->save($data);
        return new ImportResult('Success');
    }
    
    abstract protected function readFile(string $filePath): array;
    
    protected function validate(array $data): void {
        // Базовая валидация
        if (empty($data)) throw new InvalidDataException();
    }
    
    protected function transform(array &$data): void {
        // Перекрытие в подклассах
    }
    
    abstract protected function save(array $data): void;
}

class CSVUserImporter extends DataImporter {
    protected function readFile(string $filePath): array {
        // CSV парсинг
        return array_map('str_getcsv', file($filePath));
    }
    
    protected function validate(array $data): void {
        parent::validate($data);
        // Дополнительная валидация для пользователей
        foreach ($data as $row) {
            if (!filter_var($row['email'], FILTER_VALIDATE_EMAIL)) {
                throw new InvalidEmailException();
            }
        }
    }
    
    protected function save(array $data): void {
        // Сохранение пользователей
    }
}

8. Dependency Injection Pattern

Это основа чистой архитектуры, использую везде.

// Service Container (Laravel)
app()->bind(UserRepositoryInterface::class, function() {
    return new UserRepository(new PDO('...'));
});

app()->bind(EmailService::class, function() {
    return new MailerEmailService(Mail::mailer());
});

// Автоматическое внедрение в контроллер
class UserController {
    public function __construct(
        private UserRepositoryInterface $userRepository,
        private EmailService $emailService
    ) {}
    
    public function store(Request $request) {
        // Зависимости автоматически инжектируются
        $user = User::create($request->validated());
        $this->emailService->sendWelcome($user);
        return response()->json($user);
    }
}

Какие паттерны использую редко?

// Singleton - избегаю (усложняет тестирование)
// Static методы - минимизирую
// Global переменные - не использую
// Adapter - используется в legacy интеграциях
// Proxy - для ленивой загрузки, очень редко

Мой подход к паттернам

  1. Не переусложняю - использую паттерн, если он решает реальную проблему
  2. KISS - простой код лучше чем паттерн ради паттерна
  3. Читаемость - код должен быть понятен новичку в проекте
  4. Testability - паттерны должны облегчить тестирование
  5. Flexibility - паттерны должны дать возможность менять реализацию

Паттерны - это инструменты, а не цель. Цель - чистый, тестируемый, поддерживаемый код.