Composite Design Pattern bentuknya seperti hierarki objek pada Tree Structures. Design pattern ini biasanya terdiri dari satu interface yang disebut Component yang menjadi abstraksi untuk beberapa class implementation. Implementasi dari interface Component tersebut diantaranya sebagai objek tunggal yang disebut Leaf, lalu ada implementasi lainnya sebagai objek yang menampung beberapa Component tersebut yang disebut Composite. Kedua jenis implementasi itu sama-sama mengimplementasi interface yang sama sehingga implementation Composite tadi bisa menampung baik Leaf maupun sesama Composite. Implementation Composite tersebut bertugas menghandle kedua jenis implementation Component sebagai sebuah hierarki Tree Stuctures. Jadi objek-objek Leaf yang ditampung pada Composite itu bisa dihandle masing-masing satu-persatu berdasarkan hierarki. Agak kompleks memang design pattern satu ini🤯. Gw aja mikirin contoh use case-nya aja perlu mikir keras😵.
Composite Design Pattern adalah Structural Design Pattern yang bertugas menyusun beberapa objek menjadi sebuah hierarki seperti Tree Structures dan masing-masing objek tersebut bisa dihandle seperti kesatuan objek.
Composite Design Pattern
Use Case
Kita akan membuat beberapa objek seputar hierarki struktur militer. Hierarki tertinggi pada militer adalah Perwira dan Inspektur Sarjana yang menjadi atasan dari Bintara. Seorang Perwira juga bisa menjadi atasan dari Perwira lainnya, seperti Perwira tinggi yang menjadi atasan dari Perwira lain. Logikanya adalah perwira tinggi akan memberikan perintah yang akan diperintahkan ke perwira bawahannya, lalu bawahan tersebut juga akan meneruskan perintah tersebut ke bawahannya yang lebih rendah lagi. Begitu seterusnya hingga perintah tersebut dilaksanakan oleh para bawahannya.
Contoh code
Interface Military
public interface Military{
void command(String command);
String name();
String rank();
}
Class Bintara
public class Bintara implements Military{
private final String name;
private final String rank;
public Bintara(String name, String rank){
this.name = name;
this.rank = rank;
}
@Override
public void command(String command){
System.out.println(rank + " " + name + " doing " + command);
}
@Override
public String name(){
return this.name;
}
@Override
public String rank(){
return this.rank;
}
}
Class Perwira
public class Perwira implements Military{
private final String name;
private final String rank;
public Perwira(String name, String rank){
this.name = name;
this.rank = rank;
}
@Override
public void command(String command){
System.out.println(this.rank + " " + this.name + " giving command to its subordinates to " + command);
}
@Override
public String name(){
return this.name;
}
@Override
public String rank(){
return this.rank;
}
}
Class UndergraduateInspector
public class UndergraduateInspector implements Military{
private final String name;
private final String rank;
private final String title;
public UndergraduateInspector(String name, String rank, String title){
this.name = name;
this.rank = rank;
this.title = title;
}
@Override
public void command(String command){
System.out.println(this.rank + " " + this.title + " " + this.name + " giving command to its subordinates to " + command);
}
@Override
public String name(){
return this.title + " " + this.name;
}
@Override
public String rank(){
return this.rank;
}
}
Contoh penggunaan
public static void main(String[] args){
Military andi = new Bintara("Andi", "Brigadir");
Military budi = new Bintara("Budi", "Bripda");
Military coki = new Bintara("Coki", "Briptu");
Military dodi = new Bintara("Dodi", "Bripka");
Military elmi = new Perwira("Elmi", "Iptu");
List<Military> elmiSubordinates = Arrays.asList(andi, budi, coki, dodi);
Military fiki = new Bintara("Fiki", "Bripda");
Military gani = new Bintara("Gani", "Briptu");
Military heri = new Bintara("Heri", "Brigadir");
Military indi = new Bintara("Indi", "Bripka");
Military joni = new UndergraduateInspector("Joni", "Ipda", "dr.");
List<Military> joniSubordinates = Arrays.asList(fiki, gani, heri, indi);
Military kiki = new Perwira("Kiki", "Irjen");
List<Military> kikiSubordinates = Arrays.asList(elmi, joni);
Map<String, List<Military>> kikiSecondSubordinatesMap = new LinkedHashMap<>();
kikiSecondSubordinatesMap.put(elmi.name(), elmiSubordinates);
kikiSecondSubordinatesMap.put(joni.name(), joniSubordinates);
String command = "Back to HQ!";
kiki.command(command);
for(Military kikiSubordinate : kikiSubordinates){
kikiSubordinate.command(command);
List<Military> militaries = kikiSecondSubordinatesMap.get(kikiSubordinate.name());
for(Military military : militaries){
military.command(command);
}
}
}
Kita mendaftarkan masing-masing hierarki menggunakan List di client code. Kita juga menulis logic masing-masing hierarki di client code.
Masalah
Karena semuanya ditulis di client code, tentu ribet memaintain code seperti ini. Selain itu logic juga diekspos di client code, sehingga melanggar Single Responsibility Principle. Setiap perubahan nantinya tentu memerlukan effort yang besar.
Solusi
Permasalahan di atas bisa diselesaikan menggunakan Composite Design Pattern😎.
Interface Military
public interface Military{
void command(String command);
String name();
String rank();
}
Interface SuperiorMilitary
public interface SuperiorMilitary extends Military{
void insertSubordinates(Military... subordinates);
List<Military> getSubordinates();
boolean removeSubordinate(Military subordinate);
}
Class Bintara
public class Bintara implements Military{
private final String name;
private final String rank;
public Bintara(String name, String rank){
this.name = name;
this.rank = rank;
}
@Override
public void command(String command){
System.out.println(rank + " " + name + " doing " + command);
}
@Override
public String name(){
return this.name;
}
@Override
public String rank(){
return this.rank;
}
}
Class Perwira
public class Perwira implements SuperiorMilitary{
private final String name;
private final String rank;
private final List<Military> subordinates;
public Perwira(String name, String rank){
this.name = name;
this.rank = rank;
this.subordinates = new ArrayList<>();
}
@Override
public void command(String command){
StringJoiner subordinateNames = new StringJoiner(", ");
for(Military subordinate : subordinates){
subordinateNames.add(subordinate.rank() + " " + subordinate.name());
}
System.out.println(this.rank + " " + this.name + " giving command to " + subordinateNames + " to " + command);
for(Military subordinate : subordinates){
subordinate.command(command);
}
}
@Override
public String name(){
return this.name;
}
@Override
public String rank(){
return this.rank;
}
@Override
public void insertSubordinates(Military... subordinates){
Collections.addAll(this.subordinates, subordinates);
}
@Override
public List<Military> getSubordinates(){
return Collections.unmodifiableList(this.subordinates);
}
@Override
public boolean removeSubordinate(Military subordinate){
return this.subordinates.remove(subordinate);
}
}
Class UndergraduateInspector
public class UndergraduateInspector implements SuperiorMilitary{
private final String name;
private final String rank;
private final String title;
private final List<Military> subordinates;
public UndergraduateInspector(String name, String rank, String title){
this.name = name;
this.rank = rank;
this.title = title;
this.subordinates = new ArrayList<>();
}
@Override
public void command(String command){
StringJoiner subordinateNames = new StringJoiner(", ");
for(Military subordinate : subordinates){
subordinateNames.add(subordinate.rank() + " " + subordinate.name());
}
System.out.println(this.rank + " " + this.title + " " + this.name + " giving command to " + subordinateNames + " to " + command);
for(Military subordinate : subordinates){
subordinate.command(command);
}
}
@Override
public String name(){
return this.title + " " + this.name;
}
@Override
public String rank(){
return this.rank;
}
@Override
public void insertSubordinates(Military... subordinates){
Collections.addAll(this.subordinates, subordinates);
}
@Override
public List<Military> getSubordinates(){
return Collections.unmodifiableList(this.subordinates);
}
@Override
public boolean removeSubordinate(Military subordinate){
return this.subordinates.remove(subordinate);
}
}
Contoh penggunaan
public static void main(String[] args){
Military andi = new Bintara("Andi", "Brigadir");
Military budi = new Bintara("Budi", "Bripda");
Military coki = new Bintara("Coki", "Briptu");
Military dodi = new Bintara("Dodi", "Bripka");
SuperiorMilitary elmi = new Perwira("Elmi", "Iptu");
elmi.insertSubordinates(andi, budi, coki, dodi);
Military fiki = new Bintara("Fiki", "Bripda");
Military gani = new Bintara("Gani", "Briptu");
Military heri = new Bintara("Heri", "Brigadir");
Military indi = new Bintara("Indi", "Bripka");
SuperiorMilitary joni = new UndergraduateInspector("Joni", "Ipda", "dr.");
joni.insertSubordinates(fiki, gani, heri, indi);
SuperiorMilitary kiki = new Perwira("Kiki", "Irjen");
kiki.insertSubordinates(elmi, joni);
kiki.command("Back to HQ!");
kiki.removeSubordinate(joni);
kiki.command("Counter Attack!");
}
Object Diagram dari design di atas jadi seperti berikut:
Class Bintara (Leaf Component) mengimplementasi interface Military (Component). Interface SuperiorMilitary (Composite Component) meng-extend interface Military dengan tambahan behavior khusus yaitu mengontrol anak buahnya. Class Perwira dan class UndergraduateInspector sama-sama mengimplementasi interface SuperiorMilitary. Keduanya memiliki kontrol terhadap bawahannya.
Kenapa menggunakan Composite Design Pattern?
Dengan Composite Design Pattern penggunaannya di client code jadi lebih simple. Kita memiliki Leaf Component sebagai bawahan dan Composite Component sebagai atasan yang menghandle beberapa Leaf Component & Composite Component lainnya yang bisa digunakan berbarengan. Kita dapat menerapkan Iterator Design Pattern untuk menghandle Composite Component. Antar implementation tersebut interchangable, kita bisa register siapa bawahan siapa tanpa menampilkan kompleksitasnya ke client code dan membuang bawahan yang sudah tidak diperlukan secara flexible. Pada contoh di atas, jika perwira Kiki juga memiliki bawahan perwira baru yang juga memiliki bawahan, kita tinggal register lagi implementasinya ke perwira Kiki, sisanya akan dihandle oleh masing-masing implementasi.
Verdict
Composite Design Pattern ini lumayan kompleks. Design pattern ini terdiri dari interface Component sebagai abstraksi paling umum, Leaf Component sebagai implementasi Component tunggal, dan Composite Component sebagai implementasi Component yang dapat menghandle beberapa Component, baik itu Leaf Component maupun Composite Component lainnya. Sejauh ini gw belum pernah menemukan kasus sekompleks ini dunia kerja. Ini juga gw agak susah mencari contoh kasus yang sederhana😅.