
Template Method Design Pattern cukup sering digunakan dalam library Java seperti AbstractList, AbstractSet, dan AbstractMap. Design Pattern ini berbasis inheritance. Walaupun mungkin terdengar kurang familiar, tapi pemanfaatan design pattern ini cukup sering dipraktekkan pada beberapa kasus. Bagi yang pernah menggunakan Spring Framework juga mungkin udah familiar dengan desgin pattern ini seperti saat meng-override config class di Spring seperti GenericFilterBean di Spring MVC atau WebSecurityConfigurerAdapter di Spring Security. Tingkat kompleksitasnya termasuk rendah sehingga gampang dimengerti.
Template Method Design Patttern adalah Behavioral Design Pattern yang menggunakan beberapa kerangka algoritma pada superclass dan dapat di-override oleh subclass secara spesifik tanpa mengubah strukturnya, lalu implementasi dari kerangka-kerangka tersebut dapat dieksekusi lewat helper method.
Design Pattern
Use Case
Kita ingin membuat aplikasi yang menampilkan resep minuman sesuai minuman yang diinginkan.
- Terdapat beberapa jenis minuman yang akan ditampilkan resepnya;
- Pertama Coffee, komposisinya kopi, diseduh dengan air panas;
- Kedua, Ice Tea, resepnya teh, diseduh dengan air dan es batu;
- Lalu masing-masing bahannya diaduk sebelum disediakan;
Contoh Code
public abstract class Drink{
public abstract void prepareComposition();
public abstract void prepareWater();
}public class Coffee extends Drink{
@Override
public void prepareComposition(){
System.out.println("pour coffee into cup");
}
@Override
public void prepareWater(){
System.out.println("add hot water");
}
}public class IceTea extends Drink{
@Override
public void prepareComposition(){
System.out.println("put tea into cup");
}
@Override
public void prepareWater(){
System.out.println("add some water with ice");
}
}public static void main(String[] args){
Drink drink = new Coffee();
makeDrink(drink);
System.out.println();
Drink drink2 = new IceTea();
makeDrink(drink2);
}
private static void makeDrink(Drink drink){
drink.prepareComposition();
drink.prepareWater();
System.out.println("mix it for a while");
System.out.println("your drink is ready to serve");
}
Kita menggunakan Drink sebagai abstract class dengan abstract method prepareComposition() dan prepareWater() yang nantinya di-override oleh subclass IceTea dan Coffee. Lalu kita menambahkan sebuah static method yang berfungsi menampilkan resep dan langkah-langkah pembuatan minuman.
Masalah
Sebenarnya sah-sah saja membuat code seperti di atas. Tapi code pada method makeDrink() untuk menampilkan langkah-langkah itu sudah template, sudah fixed seperti itu behavior-nya. Sehingga jika code tersebut mau digunakan di tempat lain, otomatis kita harus copy-paste manual method makeDrink() tersebut. Walaupun bisa juga dibuat public utils class untuk itu tapi dalam hal ini tindakan tersebut dianggap bad practice karena static utils itu lebih praktis digunakan untuk pure function. Pada contoh di atas, method prepareComposition() atau prepareWater() bisa saja turunannya melakukan mutasi sehingga ada side effect. Selain itu kita mengekspose method prepareComposition() dan prepareWater() ke public. Seharusnya itu bukan konsumsi publik karena bagaimana cara minuman itu dibuat harusnya jadi "urusan dapur" masing-masing objek.
Solusi
Sekarang kita coba pendekatan Template Method Design Pattern😎.
public abstract class Drink{
protected abstract void prepareComposition();
protected abstract void prepareWater();
public final void make(){
prepareComposition();
prepareWater();
System.out.println("mix it for a while");
System.out.println("your drink is ready to serve");
}
}public class Coffee extends Drink{
@Override
protected void prepareComposition(){
System.out.println("pour coffee into cup");
}
@Override
protected void prepareWater(){
System.out.println("add hot water");
}
}public class IceTea extends Drink{
@Override
protected void prepareComposition(){
System.out.println("put tea into cup");
}
@Override
protected void prepareWater(){
System.out.println("add some water with ice");
}
}public static void main(String[] args){
Drink coffee = new Coffee();
coffee.make();
System.out.println();
Drink iceTea = new IceTea();
iceTea.make();
}Di sini ada 2 jenis method, yaitu skeleton method yang berisi kerangka algoritma spesifik untuk hal-hal tertentu yang bisa di-override oleh turunannya, dan helper method yang menjadi trigger untuk mengeksekusi kerangka-kerangka tersebut. Sekarang method makeDrink() kita pindahkan pada method make() pada Abstract Class Drink sebagai helper method, sehingga tidak perlu lagi copy paste manual setiap melakukan eksekusinya di berbagai tempat. Selain itu dengan Design Pattern ini kita bisa mengurangi encapsulation pada skeleton method seperti pada method prepareComposition() dan prepareWater() dari public menjadi protected sehingga hanya turunannya saja yang tahu "urusan dapur" objek tersebut. Method make() yang jadi helper boleh kita bikin final agar strukturnya tidak berubah dan tidak dapat di-override oleh subclass-nya. Subclass tugasnya hanya memodifikasi algoritma di skeleton method yang ingin diubah saja behaviornya. User yang ingin menggunakan object ini tinggal memanggil helper method-nya saja untuk eksekusi.
Kenapa menggunakan Template Method Design Pattern?
Dengan Template Method, kita bisa memecah monolitik algoritma menjadi beberapa kerangka individu algoritma yang didesain pada subclass, tapi tetap mengikuti struktur algoritma dari superclass. Design Pattern ini sebaiknya digunakan hanya jika subclass-nya memiliki behavior yang mirip dengan superclass. Selain itu, penggunaan design pattern ini biasanya jadi cenderung sulit di-maintain jika terdapat banyak skeleton method. Selain menggunakan abstract class, sebenarnya juga bisa diimplementasikan menggunakan interface. Apalagi sejak Java 8 interface memiliki keyword default method untuk default behavior pada interface. Tapi kelemahan interface untuk design pattern ini adalah default method bisa dengan bebas di-override oleh subclass, sedangkan abstract class bisa menggunakan final keyword pada method untuk mencegah subclass meng-override helper method. Lalu by default, abstract method pada interface sifatnya public, tidak bisa dibuat protected atau package-private, sedangkan pada kasus ini kita tidak ingin mengeksploitasi skeleton method ke luar.
Verdict
Salah satu contoh yang paling sering ditemukan adalah ketika ingin mengatur behavior Spring Security dengan membuat class baru lalu extend class WebSecurityConfigurerAdapter dan override behavior yg diperlukan. Jadi dengan Template Method Design Pattern, kita hanya meng-override kerangka method yang diperlukan saja tanpa mengganti atau menulis ulang keseluruhan behavior. Perubahan behavior tersebut tidak mengganggu struktur dari superclass.
