Wzorzec projektowy Kompozyt (Composite) to jeden ze strukturalnych wzorców projektowych opisanych przez Gang of Four. W tym artykule poznasz zasadę działania tego wzorca oraz jego przykładowe zastosowania. Ten wpis jest kolejnym wpisem z serii o wzorcach projektowych. Jeśli chcesz poznać inne wzorce projektowe lub dowiedzieć się czym są wzorce projektowe, to koniecznie sprawdź mój wpis o wzorcach projektowych. Przykłady kodu zostały przygotowane w TypeScript.
Opis wzorca Kompozyt
Kompozyt jest wzorcem, który znajdzie swoje zastosowanie w aplikacjach, gdzie wykorzystywane są struktury drzewiaste. Celem Kompozytu jest uporządkowanie struktur hierarchicznych (drzewiastych) w kodzie. Drugi cel to zapewnienie jednolitego interfejsu, który będzie implementowany przez wszystkie elementy składające się na tę strukturę. Banda Czworga cel Kompozytu definiuje następująco:
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Elementy składowe Kompozytu
W zrozumieniu co składa się na Kompozyt, pomoże poniższy diagram klas.
Na Kompozyt składają się:
Leaf
— liść, czyli komponent, który nie ma swoich potomków;Composite
— komponent, który może posiadać swoich potomków;Component
— interfejs wymuszający implementację wspólnej metody lub wielu metod dla wszystkich klas. W zależności od aplikacji, zamiast metodyoperation
w interfejsie będzie znajdować się inna metoda lub metody implementowane zarówno przez liście, jak i przez komponenty mogące mieć swoich potomków;- niektóre źródła podają czwarty element wzorca, którym jest
Client
, czyli obiekt, który manipuluje obiektami występującymi w strukturze z wykorzystaniem metod zdefiniowanych w interfejsieComponent
. Dla uproszczenia jednak moim zdaniem można go pominąć. Kod piszemy po to, by ktoś/coś go wykorzystywała, więc moim zdaniem fakt istnienia klienta jest oczywisty.
Przykłady wykorzystania Kompozytu
Pierwszym przykładem wykorzystania Kompozytu, jaki przyszedł mi do głowy, jest aplikacja generująca hierarchiczne dokumenty wykorzystujące zagnieżdżone struktury. Może to być np. aplikacja w JavaScript generująca dokumenty HTML. Struktura elementów HTML mogłaby być reprezentowana w kodzie przez następującą strukturę.
Liśćmi w przedstawionym przykładzie jest Text
, czyli klasa reprezentująca tekst, który nie zawiera już kolejnych elementów potomnych. Natomiast wszystkie pozostałe klasy to elementy HTML, które mogą posiadać potomków. Przeanalizuj na spokojnie poniższy przykład.
interface IElement {
id: string;
render(): string;
}
abstract class HtmlElement implements IElement {
public readonly id: string;
private readonly _children: Map<string, IElement> = new Map();
public constructor( private readonly _tag = '' ) {
this.id = crypto.randomUUID();
}
public addChild( element: IElement ) {
this._children.set( element.id, element );
}
public removeChild( id: string ) {
this._children.delete( id );
}
public render(): string {
const childrenContent: string[] = [];
for ( const child of this._children.values() ) {
childrenContent.push( child.render() );
}
return `<${ this._tag }>${ childrenContent.join( '' ) }</${ this._tag }>`;
}
}
class Html extends HtmlElement {
public constructor() {
super('html');
}
}
class Body extends HtmlElement {
public constructor() {
super('body');
}
}
class Paragraph extends HtmlElement {
public constructor() {
super('p');
}
}
class TextContent implements IElement {
public readonly id: string;
public constructor( private readonly _text: string ) {
this.id = crypto.randomUUID();
}
public render(): string {
return `${ this._text }`;
}
}
const html = new Html();
const body = new Body();
const paragraph1 = new Paragraph();
const paragraph2 = new Paragraph();
const text1 = new TextContent( 'Hello,' );
const text2 = new TextContent( 'World!' );
html.addChild( body );
body.addChild( paragraph1 );
paragraph1.addChild( text1 )
paragraph1.addChild( paragraph2 );
paragraph2.addChild( text2 );
console.log( html.render() ); // <html><body><p>Hello,<p>World!</p></p></body></html>
console.log( body.render() ); // <body><p>Hello,<p>World!</p></p></body>
Implementacja metody render
w klasie HtmlElement
rekurencyjnie renderuje wszystkie elementy potomne aż do napotkania przypadku podstawowego, czyli sytuacji, gdy element metoda render
elementu potomnego zwraca string
. Możliwe jest również renderowanie części drzewa poprzez wywołanie metody render
dla dowolnego elementu drzewa. Klient nie musi wtedy wiedzieć, że w tym przypadku obiekt body
ma w sobie kolejne dzieci do wyrenderowania. Dla czytelności zdecydowałem się też na skorzystanie z dziedziczenia i stworzenie odpowiednich klas dla każdego elementu HTML, choć z technicznego punktu widzenia nie jest to konieczne.
Przykład został mocno uproszczony na potrzeby artykułu. W praktyce struktura ta byłaby znacznie bardziej rozbudowana. Dla elementów HTML powinna być możliwość np. definiowania atrybutów. Samych elementów również powinno być znacznie więcej.
W podobny sposób można by zaimplementować np. strukturę plików i katalogów, gdzie liściem jest pojedynczy plik.
Podsumowanie
Kompozyt ma zastosowanie w ściśle określonym przypadku, czyli w aplikacjach wykorzystujących struktury drzewiaste. Jeśli miałbyś/miałabyś zapamiętać jedno z tego artykułu, to niech to będzie właśnie sytuacja, kiedy ten wzorzec może się przydać i jakie problemy rozwiązuje. Aby przypomnieć sobie szczegóły, zawsze możesz tu wrócić. Bardzo jestem ciekaw czy Ty miałeś/aś styczność z Kompozytem w praktyce. Zachęcam do opisania swoich doświadczeń w komentarzu pod postem 🙂
Książkę Design Patterns: Elements of Reusable Object-Oriented Software możesz kupić klikając w TEN link. Kupując z tego linku, wspierasz rozwój bloga.
Źródła i materiały dodatkowe
- Design Patterns: Elements of Reusable Object-Oriented Software – Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994) str. 183
- Wzorzec projektowy kompozyt (ang. Composite)
- Wzorzec – Composite (Kompozyt) – Jarosław Czub
- Refactoring Guru – Composite
- Source Making – Composite Design Pattern
- Design patterns cheat sheet
- Kompozyt (Composite) – wzorzec projektowy – java.edu.pl
- Composite (Kompozyt) – Koddlo
Zapisz się na mailing i odbierz e-booka
Odbierz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer i realnie zwiększ swoje szanse na rozmowie rekrutacyjnej! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.