wtorek, 17 lipca 2012

ten pieprzony init()

ten pieprzony init

Zend w wersji 1.x obfitował w klasy, które posiadały deklarację pustej metody init(), która była wywoływana w konstruktorze.

Do czego jest ona wykorzystywana? Twórcy Zenda doszli do wniosku, że jeżeli chcesz np. stworzyć odpowiednio skonfigurowany formularz (np. do dodawania produktów), to idealnym rozwiązaniem będzie rozszerzenie klasy Zend_Form i umieszczenie całej kofiguracji owego formularza w nadpisanej metodzie init(), która wykona się przy tworzeniu nowego obiektu.

Sprytne, no nie? I jeszcze w dodatku można pokusić się o stwierdzenie, że jest to implementacja wzorca template method.

Chciałbym jednak zasmucić wszystkich tych, którzy praktykują takie rozwiązanie. Ani to sprytne nie jest, a użycie wzorca jest niepoprawne i niepotrzebne.

problem jest dużo większy

Przykład z Zenda, to tylko wstęp, demonstracja problemu. W ogóle, problemem są wszelkie dziedziczenia, które mają na celu stworzenie klasy, której obiekty będą po prostu odpowiednio skonfigurowanymi instancjami rodzica np.
<?php
class Field
{
  private $_name;

  public function __construct($name)
  {
    $this->_name = $name;
  }
  /* code */
}

class Form
{
  private $_fields = array();

  public function addField(Field $field)
  {
    $this->_fields[] = $field;
  }
  /* code */
}

class ProductForm extends Form
{
  public function __construct()
  {
    $this->addField(new Field('name'));
    $this->addField(new Field('description'));
    $this->addField(new Field('price'));
  }
}
Czym jest klasa ProductForm? Dlaczego rozszerza klasę Form? Czym się różni? Jaką nową funkcjonalność dodaje? Modyfikuje?

never do that again

Jeżeli zdarzyło Ci się stosować takie rozwiązanie, to przestań!
Dziedziczenie to bardzo silna i wiążąca zależność pomiędzy klasami i powinno się ją wykorzystywać tylko i wyłącznie w sytuacjach, gdy jest naprawdę konieczna.

co dalej?

Czy to oznacza, że tego typu obiekty należy tworzyć tam, gdzie są wykorzystywane? Budować je w tych miejscach? A co, gdy są wykorzystywane dwukrotnie, w dwóch różnych miejscach? Co gdy ich konfiguracja, to 20-30 i więcej linijek kodu?

Jest doskonałe rozwiązanie tego problemu. Nazywa się Abstract Factory:)

Kod z przykładu, bez dziedziczenia, z użyciem wzorca, po refaktoryzacji, wygląda tak:
<?php
class ProductFormFactory
{

  /**
   * @return Form
   */
  public function create()
  {
    $form = new Form();

    $form->addField(new Field('name'));
    $form->addField(new Field('description'));
    $form->addField(new Field('price'));

    return $form;
  }
}
Czy tracimy coś na takim rozwiązaniu? Nie. Co zyskujemy? Unikamy dziedziczenia, klasa tworząca instancje klasy Form nie jest w tak dużym stopniu od niej zależna. Nie tworzymy zależności, której nie ma potrzeby tworzyć.