Kali ini gw akan mengupas tuntas tentang Strategy Design Pattern, yang sebenarnya sudah umum digunakan oleh software engineer. Barangkali masih ada yang bingung sama design pattern yang satu ini. Tingkat kompleksitasnya cukup rendah, sehingga seharusnya gampang dipahami asalkan punya basic OOP yang baik. Oh ya, karena design pattern ini sangat bergantung pada interface, jika masih asing dengan interface ada baiknya baca-baca terlebih dahulu tulisan terbaru gw tentang Programming to interface sebelum lanjut membaca tulisan ini.
Strategy Design Pattern adalah Behavioral Design Pattern yang memberikan kita kebebasan untuk menentukan family algoritma yang dipisah ke dalam beberapa kelas berbeda sehingga penggunaan implementasi objeknya fleksibel.
Design Pattern
Use Case
Misalkan kita ingin membuat sebuah aplikasi action/arcade gaming menggunakan console PlayStation dengan spesifikasi seperti berikut:
- Kita butuh object yang dapat mengimplementasi behavior Player berdasarkan tombol console yang digunakan;
- Ketika memanggil method push triangle, maka player akan melakukan serangan dengan sebuah tinju (punch) dengan behavior yang sudah ditentukan lewat parameter;
- Ketika memanggil method push cross, maka player akan melakukan serangan dengan sebuah tendangan (kick) dengan behavior yang sudah ditentukan lewat parameter;
- Terdapat beberapa jenis algoritma untuk melakukan tinju dan melakukan tendangan;
- Masing-masing object yang dihasilkan dapat menggunakan algoritma manapun saat melakukan tinju atau tendangan secara fleksibel;
Untuk memperpendek tulisan, gw hanya mengimplementasi dua tombol dengan dua jenis serangan di sini😁.
Contoh code
Player Class
public class Player{
public void pushTrianlge(String mode){
if(mode.equals("sword")){
System.out.println("punch with sword");
}
if(mode.equals("uppercut")){
System.out.println("do uppercut punch");
}
}
public void pushCross(String mode){
if(mode.equals("flying kick")){
System.out.println("do flying kick to opponent");
}
if(mode.equals("lower kick")){
System.out.println("do low kick to opponent's leg");
}
}
}
Contoh Penggunaan
public static void main(String[] args){
Player yohsimitsu = new Player();
yohsimitsu.pushCross("flying kick");
yohsimitsu.pushTrianlge("sword");
Player paul = new Player();
paul.pushCross("flying kick");
paul.pushTrianlge("uppercut");
}
Code di atas sudah berjalan baik sesuai use case yang telah ditentukan. Kalau sudah sesuai requirement lalu masalahnya di mana?
Masalah
Misalkan di kemudian hari terjadi development yang mengharuskan kita melakukan perubahan atau penambahan algoritma terhadap cara melakukan tinju atau tendangan, seperti Taekwondo kick, pukulan siku, dan lain-lain. Hal-hal seperti ini sangat sering terjadi saat melakukan development sebuah aplikasi. Jika masih menggunakan metode di atas, maka kodenya akan semakin panjang seiring dengan semakin banyaknya perubahan algoritma. Selain itu, juga bakal terancam berbagai code conflict jika beberapa engineer berbeda melakukan perubahan pada class yang sama. Dan ini juga termasuk pelanggaran Single Responsibility Principle dan Open/Close Principle seperti yang pernah dibahas.
Solusi
Saatnya kita refactor menggunakan Strategy Pattern😎.
Player Class
public class Player{
public void pushTrianlge(Punch punch){
punch.punchAction();
}
public void pushCross(Kick kick){
kick.kickAction();
}
}
Punch Interface
public interface Punch{
void punchAction();
}
SwordPunch Class
public class SwordPunch implements Punch{
@Override
public void punchAction(){
System.out.println("punch with sword");
}
}
UppercutPunch Class
public class UppercutPunch implements Punch{
@Override
public void punchAction(){
System.out.println("do uppercut punch");
}
}
Kick Interface
public interface Kick{
void kickAction();
}
FlyingKick Class
public class FlyingKick implements Kick{
@Override
public void kickAction(){
System.out.println("do flying kick to opponent");
}
}
LowerKick Class
public class LowerKick implements Kick{
@Override
public void kickAction(){
System.out.println("do low kick to opponent's leg");
}
}
Contoh penggunaan
public static void main(String[] args){
Player yoshimitsu = new Player();
yoshimitsu.pushCross(new FlyingKick());
yoshimitsu.pushTrianlge(new SwordPunch());
Player paul = new Player();
paul.pushCross(new FlyingKick());
paul.pushTrianlge(new UppercutPunch());
}
Nah, dengan begini setiap terjadinya perubahan atau penambahan algoritma, tinggal implementasi di class baru saja tanpa mengubah code yang sudah ada sebelumnya. Setiap algoritma sudah independent. Jumlah baris code jadi lebih sedikit. Ga perlu nambah-nambahin if/else atau switch/case dengan branches yang akan sangat banyak.
Kenapa menggunakan Strategy Pattern?
Tidak semua if/else bisa diganti dengan Strategy Pattern. Misalkan code-nya tidak kompleks atau jarang banget dimungkinkan terjadinya perubahan, maka penggunaan Strategy Pattern jadi terlalu overkill. Strategy Pattern cocok digunakan pada use case di atas karena sangat mungkin terjadi perubahan atau penambahan algoritma baru yang nantinya dapat membuat kompleksitas yang tinggi. Strategy Pattern biasanya digunakan untuk code yang memiliki if/else yang kompleks atau switch/case dengan branches yang cukup banyak. Sebagian pendapat meyakini penggunaan switch/case merupakan bad practice yang sebaiknya dihindari, karena memungkinkan terjadinya penambahan algoritma baru yang akan membuat code jadi sulit di-maintain.
Verdict
Di atas gw udah membahas Strategy Pattern beserta contoh masalah dan solusinya beserta manfaatnya. Dengan begitu kita sudah menerapkan Single Responsibility Principle dan Open/Close Principle. Kalau kata salah satu mentor gw dulu, strategy pattern ini merupakan kunci awal untuk memahami design pattern. Kalau udah paham, maka belajar design pattern yang lain jadi lebih gampang katanya. Di lain waktu gw akan coba bahas design pattern lainnya😉.