Wzorzec projektowy Decorator – elastyczne dodawanie funkcjonalności

Wzorzec dekoratora jest przydatny w wielu sytuacjach, w których chcemy elastycznie dodawać funkcjonalność do istniejących obiektów. Oto kilka przykładów problemów, do których można użyć wzorca dekoratora:

  1. Dopasowywanie funkcjonalności na etapie uruchamiania: Możemy użyć dekoratora, aby dodać lub zmienić zachowanie obiektu w zależności od określonych warunków, takich jak konfiguracja aplikacji czy uprawnienia użytkownika.
  2. Logowanie i analiza: Dekorator może zostać zastosowany do rejestrowania działań obiektu, dodając logowanie przed lub po wykonaniu konkretnej metody. Na przykład, dekorator może logować czas wykonania metody lub przekazywane argumenty.
  3. Kształtowanie danych wyjściowych: W przypadku danych wyjściowych, takich jak formatowanie danych do wyświetlenia w różnych kontekstach (np. HTML, XML, JSON), dekorator może dostosowywać wyjście w zależności od wymagań.
  4. Cacheowanie: Dekorator może być użyty do dodania mechanizmu buforowania (cache) dla wyników wywołań metod, co pozwala na uniknięcie ponownego obliczania tych samych wyników dla tych samych argumentów.
  5. Autoryzacja: Dekorator może być wykorzystany do dodania mechanizmu autoryzacji przed wykonaniem danej metody. Na przykład, dekorator może sprawdzać uprawnienia użytkownika przed wykonaniem operacji.
  6. Śledzenie stanu obiektu: Dekorator może być użyty do monitorowania i rejestrowania zmian stanu obiektu. Na przykład, możemy zaimplementować dekoratory, które rejestrują historię zmian obiektu lub powiadamiają inne komponenty o aktualizacjach stanu.
  7. Kompresja danych: Dekorator może być użyty do dynamicznego kompresowania danych wyjściowych w celu zmniejszenia rozmiaru przesyłanych danych w zależności od preferencji użytkownika lub ustawień aplikacji.

Wzorzec dekoratora jest niezwykle elastyczny i pozwala na dynamiczne dostosowywanie funkcjonalności obiektów w czasie działania programu, co jest szczególnie przydatne w sytuacjach, gdy zachowanie obiektów może się zmieniać w zależności od różnych czynników.

// Interfejs komponentu
interface Component {
    public function operation(): string;
}

// Konkretny komponent
class ConcreteComponent implements Component {
    public function operation(): string {
        return "ConcreteComponent operation";
    }
}

// Dekorator bazowy
abstract class Decorator implements Component {
    protected $component;

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

    public function operation(): string {
        return $this->component->operation();
    }
}

// Konkretny dekorator
class ConcreteDecorator extends Decorator {
    public function operation(): string {
        return parent::operation() . " with added behavior in ConcreteDecorator";
    }
}

// Użycie
$component = new ConcreteComponent();
$decoratedComponent = new ConcreteDecorator($component);
echo $decoratedComponent->operation(); // Wyświetli: ConcreteComponent operation with added behavior in ConcreteDecorator

Wykorzystanie wzorca dekoratora w frameworku Symfony

Symfony jest jednym z najpopularniejszych frameworków PHP, który zapewnia solidną strukturę i wiele gotowych rozwiązań do tworzenia aplikacji webowych.

Wśród wielu wzorców projektowych, które można zastosować w Symfony, wzorzec dekoratora odgrywa ważną rolę, umożliwiając elastyczne dostosowywanie funkcjonalności aplikacji w czasie jej działania. Poniżej przyjrzymy się, jak można wykorzystać wzorzec dekoratora w Symfony na podstawie konkretnych przykładów.

Dekorowanie usług Symfony

W Symfony usługi są kluczowym elementem, który umożliwia organizację kodu i zarządzanie zależnościami. Za pomocą wzorca dekoratora możemy łatwo modyfikować zachowanie usług bez ingerencji w ich kod. Przykładowo, załóżmy, że mamy usługę do kompresji danych, ale chcielibyśmy dodać dodatkową funkcjonalność, taką jak logowanie każdego żądania kompresji. Możemy to osiągnąć poprzez utworzenie dekoratora dla tej usługi:

use Symfony\Component\DependencyInjection\Argument\WrappedIterator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

// Dekorator logujący kompresję danych
class CompressionLoggingDecorator implements DataCompressionInterface
{
    private $compressionService;
    private $logger;

    public function __construct(DataCompressionInterface $compressionService, LoggerInterface $logger)
    {
        $this->compressionService = $compressionService;
        $this->logger = $logger;
    }

    public function compress($data)
    {
        $compressedData = $this->compressionService->compress($data);
        $this->logger->info('Data compressed successfully');
        return $compressedData;
    }
}

// Konfiguracja kontenera usług Symfony
$containerBuilder = new ContainerBuilder();
$containerBuilder->register('compression_service', DataCompressionService::class);
$containerBuilder->register('compression_service_with_logging', CompressionLoggingDecorator::class)
    ->setDecoratedService('compression_service')
    ->addArgument(new Reference('compression_service'))
    ->addArgument(new Reference('logger'));

W powyższym przykładzie mamy usługę DataCompressionService, którą dekorujemy za pomocą CompressionLoggingDecorator, dodając logowanie do każdego wywołania kompresji danych.

Dekorowanie formularzy Symfony

Formularze w Symfony są wygodnym sposobem na obsługę danych wejściowych od użytkownika. Za pomocą wzorca dekoratora możemy modyfikować zachowanie formularzy w zależności od różnych warunków. Na przykład, możemy dodać walidację dodatkowych pól lub zmienić sposób wyświetlania formularza w zależności od uprawnień użytkownika. Poniżej znajduje się przykład dekorowania formularza:

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

// Dekorator formularza dodający pole "Uwagi"
class CommentFieldDecorator extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('comment', TextType::class, [
            'label' => 'Uwagi',
            'required' => false,
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'App\Entity\Comment',
        ]);
    }
}

// Klasa bazowa formularza
class CommentFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // Budowanie formularza
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'App\Entity\Comment',
        ]);
    }
}

// Użycie dekoratora do zmiany formularza
$commentForm = $this->createForm(CommentFieldDecorator::class, $comment);

W tym przykładzie dekorujemy formularz CommentFormType za pomocą CommentFieldDecorator, dodając pole „Uwagi” do istniejącego formularza.

Podsumowanie

Wzorzec dekoratora jest potężnym narzędziem, które można zastosować w Symfony do elastycznego dostosowywania funkcjonalności aplikacji bez konieczności modyfikowania oryginalnego kodu.

Dzięki zastosowaniu dekoratorów możemy łatwo rozszerzać zachowanie usług, formularzy i innych komponentów Symfony, co przyczynia się do zwiększenia modularności i elastyczności naszej aplikacji.