Źródło: https://ichef.bbci.co.uk
U wielu ludzi popularne jest powiedzenie: „zasady są po to aby je łamać”. Ja uważam, że wręcz przeciwnie. Stąpanie po kruchym lodzie, sikanie pod wiatr, świecenie latarką sobie zamiast ojcu – pewnych rzeczy nie powinno się robić i kropka. A jak to wygląda w świecie oprogramowania?
W artykule „SOLIDne programowanie” omówiłem z wami kilka ważnych reguł jak pisać kod, aby nie tylko działał ale i do czegoś się nadawał. Potem doszło jeszcze kilka porad w czasie gdy przerabialiśmy wzorce projektowe. Która z tych wszystkich zasad jest najważniejsza? Która najmniej? Którą najchętniej byście złamali?
Wszystko zależy od sytuacji – bilansu zysku i strat. Czy pracujemy nad projektem samodzielnie? Czy jest krótki czy długoterminowy? Czy płacą nam za efekt czy od godziny? I tak dalej…
Bohater dzisiejszego artykułu – wzorzec Kompozyt – łamie zasadę pojedynczej odpowiedzialności (SOLID -> S: Single Responsibility Principle), bo jednocześnie zarządza strukturą i wykonuje operacje swojej klasy.
Co dostajemy w zamian?
Kompozyt – łączy obiekty w strukturę drzewa reprezentującą hierarchię część-całość. Umożliwia klientom jednolite traktowanie pojedynczych obiektów i ich kompozycji.
Rusz głową! Wzorce projektowe
Żeby dobrze sobie zwizualizować jak działa Composite weźmy za przykład biblioteki Swing wbudowane w Javę. Pozwalają one szybko stworzyć podstawowy interfejs graficzny dla naszej aplikacji.

Wszystko zaczyna się od obiektu JFrame który jest naszym okienkiem gdzie będzie działać appka. Owe okienko możemy podzielić na segmenty – obiekty JPanel, a w nich umieścić komponenty (JComponent) takie jak grafiki, tekst, czy przyciski.
Jak widzicie, taki panel JPanel może być samotnym elementem, a może też zawierać całą masę różnych komponentów – czyli wspomnianą wyżej „kompozycję obiektów”. Zastosowanie wzorca Kompozyt pozwala traktować wszystkie te elementy tak samo. Jednakże aby to dobrze zrozumieć potrzebny będzie konkretny przykład.
Ale po kolei…
Odniosę się do artykułu ze wzorcem Polecenie – bo to właśnie tam opisywałem wam jak działa „sekwencer zaklęć„. W skrócie – sekwencer to zaklęcie, które samo nic nie robi, ale za to przechowuje w sobie 3 inne, które można odpalić jednocześnie. W kodzie naszego czarodzieja potrzebowaliśmy dodatkowych metod: addTosequencer() która umieszczała czary oraz fireSequencer() która je wyzwalała.
A teraz zastanówmy się – czy dałoby się to ogarnąć bez tych dodatkowych metod?
A i owszem. O tak: (pełny kod tutaj)
static class Wizard{
Spell spell;
void castSpell(Spell spell){
this.spell = spell;
System.out.print("(Czarodziej) Rzuca czar(y): ");
spell.castSpell();
}
– Bez sensu! Magik dalej strzela TYLKO JEDNYM zaklęciem!
– No właśnie nie. Bo Kompozyt definiuje nam to co uznajemy za to zaklęcie – to może być pojedyncza sztuczka, ale również cała ich paleta:
static class CompositeSpell implements Spell{
List<Spell> spells = new ArrayList<>();
@Override
public void castSpell() {
for (Spell spell : spells) {
spell.castSpell();
}
}

Klasa „Czar kompozytowy” (CompositeSpell) jest JEDNOCZEŚNIE jednym czarem (implementuje interfejs Spell) i kilkoma zaklęciami (przechowywanymi w liście spells).
– Na tym właśnie polega magia wzorca Kompozyt.
– Dobra, dobra! Ale idąc dalej tym podejściem, każdy czar musi mieć te same metody! A przecież jedne atakują wroga, inne przyzywają sojuszników, dają buffy, leczą czy teleportują. One wszystkie powinny implementować inne interfejsy, a wtedy Kompozyt nie zadziała!
– Nie bój się, zadziała. Musimy tylko złamać kolejną regułę:
Nie dawaj klasom metod z których nie korzystają.
Spójrzcie na to:

Aby Kompozyt spełniał swoją rolę wszystkie jego obiekty muszą współdzielić ten sam interfejs – nawet jeśli wydaje się to bez sensu, żeby np. „odesłać czar zamiast przyzwańca” (metoda unsummon()). Wtedy po prostu używamy wyjątku „Unsupperted Operation Exception” tam gdzie ta metoda nie ma działać. A klasę kompozytową uzbrajamy w klauzulę try-catch:
@Override
public void unsummon() {
for (Spell spell : spells) {
try{
spell.unsummon();
} catch (UnsupportedOperationException ignored){}
}
}
Powyższy algorytm po prostu ignoruje te czary, które nie mogą zostać odwołane. Nie jest to kodowanie najwyższych lotów, ale często jest to gra warta świeczki dzięki której zyskujemy na hermetyzacji.
ITCandidateEvaluator
Szczerze powiedziawszy: Sam nie wiem. Kompozyt nie należy ani do najłatwiejszych wzorców, ani do najpopularniejszych. Musi być konkretny powód, aby go zastosować (i przy okazji złamać parę zasad poprawnego programowania).
W książce „Wzorce Projektowe” Kompozyt świetnie się sprawdził przy tworzeniu menu w restauracji – dzięki zastosowaniu tego wzorca, jadłospis można było łatwo rozwijać o dodatkowe pozycje będące równocześnie osobnymi „podmenu”.
Być może znajdę miejsce dla kompozytu przy grupowaniu pytań rekrutacyjnych – jak wiadomo niektóre są krótkie i szybkie, zaś inne mogą prowadzić do całej gamy tzw. „follow-up questions” – pytań dodatkowych będących odpowiednikiem w/w podmenu. Zobaczymy.
Na dzisiaj zakończymy temat, ale widzimy się już wkrótce razem ze wzorcem „Stan„.