EN

Návrhové vzory (Design Patterns)

Praktický přehled principů SOLID a tvořivých, strukturálních i chování se týkajících návrhových vzorů s příklady v PHP a Drupalu.

Co tato dovednost pokrývá

Principy SOLID a klasické tvořivé (creational), strukturální (structural) a behaviorální (behavioral) návrhové vzory, každý doložený konkrétním příkladem v PHP — včetně specifických implementací pro Drupal (Plugin, Service, Hook).

Kdy ji použít

  • Navrhujete novou Drupal službu nebo modul a chcete si ji před psaním kódu ověřit vůči SOLID
  • Řešíte, jak pružně vytvářet objekty (Factory Method, Builder, Singleton) místo natvrdo zapsaného new
  • Napojujete existující třídu s nekompatibilním rozhraním do svého kódu, např. adaptujete externí SDK (Adapter)
  • Modelujete hierarchie typu část-celek, například strom renderovatelných komponent (Composite)
  • Potřebujete upozornit více posluchačů na změnu stavu objektu, aniž byste je pevně provázali (Observer)
  • Volíte algoritmus nebo strategii za běhu místo větvení podle typu (Strategy)
  • Modelujete objekt, jehož chování se mění podle vnitřního stavu, např. životní cyklus objednávky (State)
  • Vytváříte vlastní typ Drupal pluginu, injektovatelnou službu nebo implementaci hooku
  • Kontrolujete kód na anti-patterny jako God Object, zneužívání Singletonu nebo zbytečné přeinženýrství

Principy SOLID v Drupalu

SOLID je zkratka pro pět návrhových principů, které dělají návrh softwaru srozumitelnějším, flexibilnějším a snáze udržovatelným.

1. Single-Responsibility Principle (SRP) — princip jedné odpovědnosti

Definice: „Nikdy by neměl existovat více než jeden důvod, proč třídu měnit" — každá třída by měla mít jen jednu ústřední odpovědnost.

Přínosy v Drupalu:

  • Snazší testování: každou službu lze testovat nezávisle
  • Lepší organizace modulu: jasné oddělení zodpovědností
  • Znovupoužitelnost: služby lze využít v různých kontextech

Příklad:

// ❌ Violating SRP - Multiple responsibilities
class UserManager {
  public function createUser($userData) {
    // User creation logic
    $user = User::create($userData);
    $user->save();

    // Email sending logic
    $this->sendWelcomeEmail($user);

    // Logging logic
    \Drupal::logger('user')->info('User created');

    return $user;
  }
}

// ✅ Following SRP - Single responsibility per class
class UserCreationService {
  public function __construct(EmailServiceInterface $emailService) {
    $this->emailService = $emailService;
  }

  public function createUser($userData) {
    $user = User::create($userData);
    $user->save();

    $this->emailService->sendWelcomeEmail($user);
    return $user;
  }
}

2. Open-Closed Principle (OCP) — princip otevřenosti/uzavřenosti

Definice: „Softwarové entity by měly být otevřené pro rozšíření, ale uzavřené pro modifikaci"

Přínosy:

  • Rozšiřitelnost bez zásahu do stávajícího kódu
  • Nižší riziko zanesení chyb
  • Lepší udržovatelnost

Příklad v Drupalu:

// Using Drupal's Plugin System
abstract class PaymentMethodBase {
  abstract public function processPayment($amount, $currency);

  public function validatePayment($paymentData) {
    return $this->doValidation($paymentData);
  }
}

// Extension: Credit Card payment
class CreditCardPayment extends PaymentMethodBase {
  public function processPayment($amount, $currency) {
    return $this->chargeCreditCard($amount, $currency);
  }
}

3. Liskov Substitution Principle (LSP) — princip substituce

Definice: „Objekty nadtřídy by mělo být možné nahradit objekty jejích podtříd, aniž by se rozbila funkčnost aplikace"

Klíčové požadavky:

  • Předpodmínky (preconditions) nelze zpřísňovat
  • Postpodmínky (postconditions) nelze oslabovat
  • Invarianty musí zůstat zachovány
  • Signatury metod musí odpovídat

Příklad:

interface ContentEntityInterface {
  public function getTitle();
  public function setTitle($title);
  public function isPublished();
}

class Node implements ContentEntityInterface {
  public function getTitle() {
    return $this->title ?? 'Untitled Node';
  }

  public function setTitle($title) {
    $this->title = $title;
    return $this;
  }

  public function isPublished() {
    return $this->published;
  }
}

4. Interface Segregation Principle (ISP) — princip oddělení rozhraní

Definice: „Klienti by neměli být nuceni záviset na rozhraních, která nepoužívají"

Přínosy:

  • Menší, zaměřená rozhraní
  • Nižší provázanost (coupling)
  • Snazší testování a údržba

Příklad:

// ❌ Fat interface
interface ContentManagerInterface {
  public function createContent($data);
  public function sendEmail($to, $subject, $body);
  public function logActivity($activity);
  public function generateReport($type);
}

// ✅ Segregated interfaces
interface ContentCrudInterface {
  public function createContent($data);
  public function updateContent($id, $data);
  public function deleteContent($id);
}

interface EmailSenderInterface {
  public function sendEmail($to, $subject, $body);
}

interface LoggerInterface {
  public function logActivity($activity);
}

5. Dependency Inversion Principle (DIP) — princip inverze závislostí

Definice: „Závisejte na abstrakcích, ne na konkrétních implementacích"

Přínosy:

  • Volná provázanost mezi třídami
  • Snazší testování díky dependency injection
  • Vyšší flexibilita implementace

Příklad:

interface OrderStorageInterface {
  public function saveOrder($orderData);
}

class OrderService {
  public function __construct(OrderStorageInterface $storage) {
    $this->storage = $storage;
  }

  public function processOrder($orderData) {
    $this->storage->saveOrder($orderData);
  }
}

Tvořivé vzory (Creational Patterns)

Factory Method Pattern

Účel: Definovat rozhraní pro vytváření objektů, ale nechat na podtřídách, kterou třídu instanciují.

Kdy použít:

  • Když volající nemůže dopředu vědět, jaké typy objektů má vytvořit
  • Když máte mnoho objektů společného typu
  • Když chcete centralizovat logiku vytváření objektů

Příklad:

interface LoggerFactoryInterface {
  public function createLogger(): LoggerInterface;
}

class FileLoggerFactory implements LoggerFactoryInterface {
  public function createLogger(): LoggerInterface {
    return new FileLogger();
  }
}

class DatabaseLoggerFactory implements LoggerFactoryInterface {
  public function createLogger(): LoggerInterface {
    return new DatabaseLogger();
  }
}

Builder Pattern

Účel: Oddělit konstrukci složitého objektu od jeho reprezentace.

Kdy použít:

  • Když algoritmus vytváření složitého objektu má být nezávislý na jeho jednotlivých částech
  • Když proces konstrukce musí umožňovat různé reprezentace výsledku

Příklad:

class QueryBuilder {
  private $select = [];
  private $from;
  private $where = [];

  public function select($fields) {
    $this->select = is_array($fields) ? $fields : [$fields];
    return $this;
  }

  public function from($table) {
    $this->from = $table;
    return $this;
  }

  public function where($condition) {
    $this->where[] = $condition;
    return $this;
  }

  public function build() {
    $query = 'SELECT ' . implode(', ', $this->select);
    $query .= ' FROM ' . $this->from;
    if (!empty($this->where)) {
      $query .= ' WHERE ' . implode(' AND ', $this->where);
    }
    return $query;
  }
}

// Usage
$query = (new QueryBuilder())
  ->select(['id', 'name'])
  ->from('users')
  ->where('age > 18')
  ->build();

Singleton Pattern

Účel: Zajistit, že třída má pouze jednu instanci, a poskytnout k ní globální přístupový bod.

Kdy použít:

  • Když musí existovat právě jedna instance třídy
  • Když má být tato instance dostupná globálně

Příklad:

class DatabaseConnection {
  private static $instance = null;
  private $connection;

  private function __construct() {
    $this->connection = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
  }

  public static function getInstance() {
    if (self::$instance === null) {
      self::$instance = new self();
    }
    return self::$instance;
  }

  public function getConnection() {
    return $this->connection;
  }
}

Strukturální vzory (Structural Patterns)

Adapter Pattern

Účel: Převést rozhraní třídy na jiné rozhraní, které klienti očekávají.

Kdy použít:

  • Když chcete použít existující třídu s nekompatibilním rozhraním
  • Když potřebujete vytvořit znovupoužitelnou třídu spolupracující s nesouvisejícími třídami

Příklad:

interface PaymentProcessorInterface {
  public function processPayment($amount);
}

class StripePaymentProcessor {
  public function charge($amount, $currency = 'USD') {
    // Stripe-specific implementation
  }
}

class StripeAdapter implements PaymentProcessorInterface {
  private $stripeProcessor;

  public function __construct(StripePaymentProcessor $processor) {
    $this->stripeProcessor = $processor;
  }

  public function processPayment($amount) {
    return $this->stripeProcessor->charge($amount);
  }
}

Composite Pattern

Účel: Skládat objekty do stromových struktur reprezentujících hierarchie typu část-celek.

Kdy použít:

  • Když chcete reprezentovat hierarchie typu část-celek
  • Když klienti mají s jednotlivými objekty i jejich kompozicemi zacházet jednotně

Příklad:

interface ComponentInterface {
  public function render(): string;
}

class Leaf implements ComponentInterface {
  private $content;

  public function __construct($content) {
    $this->content = $content;
  }

  public function render(): string {
    return $this->content;
  }
}

class Composite implements ComponentInterface {
  private $children = [];

  public function add(ComponentInterface $component) {
    $this->children[] = $component;
  }

  public function render(): string {
    $output = '';
    foreach ($this->children as $child) {
      $output .= $child->render();
    }
    return $output;
  }
}

Observer Pattern

Účel: Definovat vztah typu jeden-k-mnoha mezi objekty, kdy při změně stavu jednoho objektu jsou informováni všichni jeho závislí odběratelé.

Kdy použít:

  • Když změna jednoho objektu vyžaduje změnu dalších objektů
  • Když má objekt umět upozornit ostatní objekty, aniž by je znal

Příklad:

interface ObserverInterface {
  public function update($data);
}

interface SubjectInterface {
  public function attach(ObserverInterface $observer);
  public function detach(ObserverInterface $observer);
  public function notify();
}

class UserRepository implements SubjectInterface {
  private $observers = [];
  private $users = [];

  public function attach(ObserverInterface $observer) {
    $this->observers[] = $observer;
  }

  public function createUser($userData) {
    $this->users[] = $userData;
    $this->notify();
  }

  public function notify() {
    foreach ($this->observers as $observer) {
      $observer->update($this->users);
    }
  }
}

class UserLogger implements ObserverInterface {
  public function update($data) {
    // Log user creation
    \Drupal::logger('user')->info('User list updated');
  }
}

Behaviorální vzory (Behavioral Patterns)

Strategy Pattern

Účel: Definovat rodinu algoritmů, zapouzdřit každý z nich a učinit je vzájemně zaměnitelnými.

Kdy použít:

  • Když existuje více způsobů, jak provést danou operaci
  • Když potřebujete zvolit algoritmus až za běhu

Příklad:

interface SortingStrategyInterface {
  public function sort(array $data): array;
}

class BubbleSortStrategy implements SortingStrategyInterface {
  public function sort(array $data): array {
    // Bubble sort implementation
    return $data;
  }
}

class QuickSortStrategy implements SortingStrategyInterface {
  public function sort(array $data): array {
    // Quick sort implementation
    return $data;
  }
}

class Sorter {
  private $strategy;

  public function setStrategy(SortingStrategyInterface $strategy) {
    $this->strategy = $strategy;
  }

  public function sort(array $data): array {
    return $this->strategy->sort($data);
  }
}

State Pattern

Účel: Umožnit objektu měnit své chování při změně vnitřního stavu.

Kdy použít:

  • Když chování objektu závisí na jeho stavu
  • Když operace obsahují rozsáhlé, víceúrovňové podmínkové bloky

Příklad:

interface OrderStateInterface {
  public function process(Order $order);
  public function cancel(Order $order);
}

class PendingState implements OrderStateInterface {
  public function process(Order $order) {
    // Process pending order
    $order->setState(new ProcessingState());
  }

  public function cancel(Order $order) {
    $order->setState(new CancelledState());
  }
}

class ProcessingState implements OrderStateInterface {
  public function process(Order $order) {
    $order->setState(new CompletedState());
  }

  public function cancel(Order $order) {
    // Cannot cancel processing order
  }
}

class Order {
  private $state;

  public function __construct() {
    $this->state = new PendingState();
  }

  public function setState(OrderStateInterface $state) {
    $this->state = $state;
  }

  public function process() {
    $this->state->process($this);
  }

  public function cancel() {
    $this->state->cancel($this);
  }
}

Drupal-specifické vzory

Plugin Pattern

Účel: Umožnit modulům poskytovat rozšiřitelnou funkcionalitu prostřednictvím pluginů.

Příklad:

/**
 * @Block(
 *   id = "custom_block",
 *   admin_label = @Translation("Custom Block"),
 *   category = @Translation("Custom")
 * )
 */
class CustomBlock extends BlockBase {
  public function build() {
    return [
      '#markup' => $this->t('Hello from custom block!'),
    ];
  }
}

Service Pattern

Účel: Poskytovat centralizované služby, které lze injektovat do dalších tříd.

Příklad (services.yml):

services:
  mymodule.custom_service:
    class: Drupal\mymodule\Service\CustomService
    arguments: ['@database', '@current_user']

Hook Pattern

Účel: Umožnit modulům měnit nebo rozšiřovat chování Drupalu.

Příklad:

function mymodule_node_presave(NodeInterface $node) {
  if ($node->getType() == 'article') {
    $node->setTitle(strtoupper($node->getTitle()));
  }
}

Doporučené postupy

  1. Vyberte správný vzor: Nevnucujte vzor tam, kde stačí jednoduché řešení
  2. Udržujte to jednoduché: Vzory mají řešit problémy, ne vytvářet komplexitu
  3. Testujte implementace: Ověřte, že vzor ve vašem kontextu skutečně funguje
  4. Dokumentujte použití: Ať je jasné, které vzory používáte a proč
  5. Refaktorujte podle potřeby: Buďte ochotni vzor změnit, když se změní požadavky

Časté anti-patterny, kterým se vyhnout

  1. God Object: Třídy, které toho vědí nebo dělají příliš mnoho
  2. Zneužívání Singletonu: Používání singletonů na vše
  3. Těsná provázanost (Tight Coupling): Třídy příliš závislé jedna na druhé
  4. Zneužívání dědičnosti: Hluboké hierarchie dědičnosti
  5. Přeinženýrství (Over-engineering): Používání vzorů tam, kde nejsou potřeba