Composite Pattern Java
Introduction
Composite pattern is a structural design pattern aims at defining the structure of group of objects in a hierarchy. The pattern can be applied where you see a group of object sharing a tree like pattern or relationship between them like file->directories, employee in department which can further drill deep down and have child departments, mostly nodes in a tree structure.
The pattern is extensively used where :
- Application need to aggregate data across the tree structure.
- Client treats each object in an hierarchy uniformly.
- Keep client contract simple & promotes uniformity.
Terminologies
- Component : Interface for objects in the composition.
- Leaf : Leaf object in composition with no children. Defines behavior via implementing component interface.
- Composite : Object containing child component. Defining relation between components and defines behavior for components having children.
In this article, we will dig deep into an example possibly a problem of LLD design and solve it via composite pattern.
Problem
Lets say you need a design a music player where you can add songs to a playlist & play . To demonstrate the application of composite pattern, I am keeping a playlist and multiple songs added as part of LLD design.
Implementation
- Defining a base component interface as below.
public interface Playable {
void play();
}
2. Defining Leaf component which is song object in our case. Leaf object also defines a behavior which is play [Song to be played.]
public class Song implements Playable{
private final String title;
private final String artist;
private final String album;
public Song(String title,String artist,String album) {
this.title = title;
this.artist = artist;
this.album = album;
}
@Override
public void play() {
System.out.println("Playing song! : " + title + " " + artist + " " + album);
}
}
3. Defining composite object which is playlist in our case & containing songs to be played by the playlist. Below code has list of songs contained by composite object Playlist.
public class Playlist implements Playable{
private final String name;
private final List<Playable> songList;
public Playlist(String name) {
this.name = name;
this.songList = new ArrayList<>();
}
@Override
public void play() {
System.out.println("Playing the playlist : " + name);
songList.forEach(Playable::play);
}
public void add(Playable song) {
//Adding to the playlist
this.songList.add(song);
System.out.println("Added to the playlist ->" + name);
}
public void remove(Playable song) {
this.songList.remove(song);
}
}
4. Reaching here, we are almost done. Now we need to see the invoker in action as below.
public class Invoker {
public static void main(String [] args) {
Playable song1 = new Song("Song-A","Artist-A","Album-A");
Playable song2 = new Song("Song-B","Artist-B","Album-B");
Playable song3 = new Song("Song-C","Artist-C","Album-C");
Playlist playlist = new Playlist("Vibhor's Playlist");
playlist.add(song1);
playlist.add(song2);
playlist.add(song3);
playlist.play();
}
}
//Playing the playlist : Vibhor's Playlist
//Playing song! : Song-A Artist-A Album-A
// Playing song! : Song-B Artist-B Album-B
//Playing song! : Song-C Artist-C Album-C
4. Keeping it till here only, you can also add/extend Music Player class and invoke playlist in the class. Hope you got the the detailed explanation about composite pattern.
Conclusion
- The composite pattern structures the hierarchy consisting of individual and composite objects & can handle complex hierarchy as well.
- Clients treats basic and composite objects uniformly through a component interface which makes client code simple.
- Adding new components is easy and client can focus on aggregated functionality provided by composite object. This also promotes loose coupling in code & adheres to ‘O’ in SOLID principles.