pondělí 27. října 2008

Problém "Božského objektu"

Setkal jsem s ním snad v každé aplikaci, ale teprve teď jsem proto našel ten správný výraz - God object.

a God object is an object that knows too much or does too much

Mluvím o objektu, který plní stěžejní úlohu v celé aplikaci, něco jako centrální mozek lidstva ze seriálu Návštěvníci. Problém tohoto objektu je v tom, že neustále bobtná, výsledkem je, že máme interface s dvaceti a více metodami, což jednoduše nepřidá na přehlednosti našeho API a to už ani nemluvím o jeho implementaci.

To, že v aplikaci existuje nějaká business fasáda, která zastřešuje vícero operací je myslím v pořádku. To co většinou nebývá v pořádku je jakým způsobem je tato fasáda vytvořena a především naimplementována. Pomůžeme si praktickým příkladem.

public interface ObjednavkovaSluzba {
    public void vytvorObjednavku(Objednavka objednavka);
    public void smazObjednavku(Objednavka objednavka);
    public void aktualizujObjednavku(Objednavka objednavka);
    public Objednavka nactiObjednavku(Serializable identifikator);
    
    public void zpracujObjednavku(Serializable identifikator);
    public void zrusObjednavku(Serializable identifikator);   
}    

Jak je vidět z rozhraní, míchají se nám tam dvě odpovědnosti (většinou to bývá i více odpovědností nežli dvě). Jednak samotné CRUD (Create, Read, Update, Delete) operace pro vytvoření objednávky a jednak operace pro zpracování objednávky. Logika věci nám velí rozhraní roztrhnout, ale jak to udělat, aby zde byla stále možnost jednoho centralizovaného rozhraní pro práci s objednávkami.

Je to velice prosté. Opravdu to roztrhneme na dvě rozhraní podle odpovědností, ale rozhraní ObjednavkovaSluzba zachováme. A zachováme ho tak, že bude složením právě těchto dvou rozhraní.

public interface CRUDOperace {
    public void vytvorObjednavku(Objednavka objednavka);
    public void smazObjednavku(Objednavka objednavka);
    public void aktualizujObjednavku(Objednavka objednavka);
    public Objednavka nactiObjednavku(Serializable identifikator);          
}   

public interface ProcesniOperace {    
    public void zpracujObjednavku(Serializable identifikator);
    public void zrusObjednavku(Serializable identifikator);   
}  
 
public interface ObjednavkovaSluzba extends CRUDOperace, ProcesniOperace {
       
}        

Díky tomuto roztržení umožníme klientům využít nejen různé pohledy na jednu fasádu (CRUD, procesní operace a nebo celek), ale umožníme mít implementaci náležející plně dané odpovědnosti. Výslednou funkcionalitu můžeme dosáhnout například složením a delegací těchto implementací.

public class CRUDOperaceImpl implements CRUDOperace {
  //...implementace vynechána
}    

public class ProcesniOperaceImpl implements ProcesniOperace {
  //...implementace vynechána
}

public class ObjednavkovaSluzbaImpl implements ObjednavkovaSluzba  {
  private CRUDOperace crudDelegate;
  private ProcesniOperace procesniDelegate;
  
  public void vytvorObjednavku(Objednavka objednavka) {
      crudDelegate.vytvorObjednavku(objednavka);       
  }  
  
  public Objednavka nactiObjednavku(Serializable identifikator) {
      procesniDelegate.nactiObjednavku(identifikator);
  
  
  //...zbytek vynechán

Tímto velice jednoduchým cvičením jsme náš Božský objekt dekomponovali na menší části, které můžeme lépe spravovat, ale zároveň jsme zachovali jednotné rozhraní, které zaštiťuje práci s centrální funkcionalitou. Takto zrefaktorované rozhraní je navíc pro klienty API zpětně kompatibilní, takže se nemusíme obávat, že bychom něco rozbili.