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.
Související články
Související epizody CZ Podcastu
- #261 · Leadership
O vedení lidí a týmů.
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi a Filemon o novinkách v Javě 6 a webových frameworcích.
- #1 · Vývojová prostředí v Javě
První díl CZ Podcastu se věnuje vývojovým prostředím pro Javu.