С какими работал паттернами проектирования?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Паттерны проектирования в моей практике
Я активно использую паттерны проектирования в своей работе. Расскажу о тех, которые применяю чаще всего и как они помогают улучшить код.
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 - для ленивой загрузки, очень редко
Мой подход к паттернам
- Не переусложняю - использую паттерн, если он решает реальную проблему
- KISS - простой код лучше чем паттерн ради паттерна
- Читаемость - код должен быть понятен новичку в проекте
- Testability - паттерны должны облегчить тестирование
- Flexibility - паттерны должны дать возможность менять реализацию
Паттерны - это инструменты, а не цель. Цель - чистый, тестируемый, поддерживаемый код.