![Contoh Template Method Design Pattern](https://images.ctfassets.net/7wjlcr9d5ii8/7DFOMoMxQaFjuz45ONccoT/e41384e83c71c2bf311719ef42ddace3/5218426053_066db7556d_o.jpg?w=1024&h=768&fl=progressive&q=100&fm=jpg&fit=thumb&f=faces)
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 terdiri dari beberapa kerangka algoritma pada superclass-nya dan kerangka tersebut dapat di-override oleh subclass-nya tanpa mengubah template strukturnya.
Design Pattern
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;
Drink Abstract Class
public abstract class Drink{
public abstract void prepareComposition();
public abstract void prepareWater();
}
Coffee Class
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");
}
}
IceTea Class
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");
}
}
Contoh penggunaan
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.
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 drink.prepareComposition() atau drink.prepareWater() bisa saja turunannya melakukan mutation sehingga ada side effect. Selain itu kita meng-expose method prepareComposition() dan prepareWater() ke public. Seharusnya itu bukan konsumsi publik karena bagaimana cara minuman itu dibuat harusnya jadi "urusan dapur" masing-masing objek.
Sekarang kita coba pendekatan Template Method Design Pattern😎.
Drink Abstract Class
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");
}
}
Coffee Class
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");
}
}
IceTea Class
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");
}
}
Contoh penggunaan
public static void main(String[] args){
Drink coffee = new Coffee();
coffee.make();
System.out.println();
Drink iceTea = new IceTea();
iceTea.make();
}
Sekarang method makeDrink() kita pindahkan pada method make() pada Drink Abstract Class sebagai template 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 kerangka 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 template boleh kita bikin final agar struktur template-nya tidak berubah, sehingga tidak dapat di-override oleh subclasses. Subclass tugasnya hanya mengganti kerangka method yang ingin diubah saja behaviornya. User yang ingin menggunakan object ini tinggal memanggil template method-nya saja.
Dengan Template Method, kita bisa memecah monolitik algoritma menjadi beberapa kerangka individu algoritma yang didesain oleh subclass, tapi tetap mengikuti struktur algoritma dari superclass. Design Pattern ini sebaiknya digunakan hanya jika subclass-nya memiliki behavior yang identik dengan superclass. Selain itu, penggunaan design pattern ini biasanya jadi cenderung sulit di-maintain jika terdapat banyak kerangka 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 template method. Lalu by default, abstract method pada interface sifatnya public, tidak bisa dibuat protected atau package-private, sedangkan pada kasus ini kita tidak ingin meng-exploitasi kerangka method ke luar.
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 object.