Strategy Pattern
Apa itu Strategy Pattern?
Strategy Pattern adalah salah-satu pola desain perilaku (behavioral design pattern) yang didefinisikan oleh "Gang of Four" (GoF). Tujuan utamanya adalah untuk mendefinisikan sebuah keluarga algoritma, membungkus (encapsulate) masing-masing algoritma tersebut, dan membuatnya dapat saling dipertukarkan (interchangeable).
Secara sederhana, pola ini memungkinkan sebuah objek (disebut Context) untuk mengubah perilakunya saat runtime dengan cara mengganti algoritma yang digunakannya. Daripada mengimplementasikan logika perilaku secara langsung di dalam objek, logika tersebut didelegasikan ke objek lain yang terpisah yang disebut Strategy.
Analogi Sederhana:
Bayangkan Anda ingin pergi dari titik A ke titik B. Anda memiliki beberapa pilihan strategi transportasi:
Naik mobil pribadi (Strategi A)
Naik bus umum (Strategi B)
Naik kereta api (Strategi C)
Tujuan Anda tetap sama (sampai di titik B), tetapi cara atau strategi untuk mencapainya bisa berbeda-beda. Anda bisa memilih strategi mana yang akan digunakan tergantung pada kondisi (misalnya, jika macet, pilih kereta; jika membawa banyak barang, pilih mobil).
Dalam pemrograman, Strategy Pattern memungkinkan "Anda" (objek Context) untuk memilih "moda transportasi" (objek Strategy) yang paling sesuai pada saat dibutuhkan, tanpa harus mengubah kode inti dari objek "Anda".
Komponen Utama Strategy Pattern
Pola ini terdiri dari tiga komponen utama:
Strategy (Interface): Ini adalah sebuah interface yang mendefinisikan sebuah metode umum untuk semua algoritma yang didukung. Dalam Go, ini adalah
interface
. Context akan berinteraksi dengan algoritma melalui interface ini.Concrete Strategy (Strategi Konkret): Ini adalah kelas atau struct yang mengimplementasikan interface Strategy. Setiap Concrete Strategy menyediakan implementasi spesifik dari sebuah algoritma. Akan ada beberapa Concrete Strategy, satu untuk setiap algoritma yang ada.
Context (Konteks): Ini adalah kelas atau struct utama yang perilakunya ingin kita ubah. Context memiliki sebuah field yang menyimpan referensi ke salah satu objek Concrete Strategy. Context tidak mengetahui detail implementasi dari masing-masing strategi, ia hanya tahu cara memanggil metode yang didefinisikan di interface Strategy. Context juga menyediakan cara (misalnya, sebuah setter method) untuk mengganti objek Strategy saat runtime.
Mengapa dan Kapan Menggunakan Strategy Pattern?
Gunakan pola ini ketika:
Anda memiliki sebuah objek dengan banyak variasi perilaku yang bergantung pada suatu kondisi. Menggunakan
if-else
atauswitch
yang besar untuk memilih perilaku ini akan membuat kode menjadi rumit dan sulit dipelihara.Anda ingin menghindari eksposisi detail implementasi algoritma yang kompleks kepada klien yang menggunakannya.
Anda perlu memungkinkan klien untuk memilih algoritma yang akan digunakan saat runtime.
Anda ingin mengikuti prinsip Desain SOLID, terutama Open/Closed Principle: sistem Anda harus terbuka untuk ekstensi (menambah strategi baru) tetapi tertutup untuk modifikasi (tidak perlu mengubah kode Context yang sudah ada).
Implementasi Lengkap Strategy Pattern di Go
Mari kita buat contoh kasus yang umum: Sistem Pembayaran E-commerce. Sebuah keranjang belanja (cart) perlu memproses pembayaran. Metode pembayarannya bisa bermacam-macam, seperti Kartu Kredit, Transfer Bank, atau E-Wallet.
Langkah 1: Definisikan Interface Strategy
Pertama, kita definisikan sebuah interface
yang akan menjadi kontrak untuk semua strategi pembayaran. Semua metode pembayaran harus memiliki fungsi Pay
.
payment_strategy.go
:
package main
// Strategy Interface
type PaymentStrategy interface {
Pay(amount float64) string
}
Langkah 2: Buat Concrete Strategy
Selanjutnya, kita buat beberapa implementasi konkret dari PaymentStrategy
. Masing-masing akan menjadi struct yang mengimplementasikan metode Pay
.
credit_card_payment.go
:
package main
import "fmt"
// Concrete Strategy 1: Credit Card
type CreditCardPayment struct {
CardNumber string
Owner string
}
func (c *CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("Membayar sejumlah $%.2f menggunakan Kartu Kredit %s", amount, c.CardNumber)
}
bank_transfer_payment.go
:
package main
import "fmt"
// Concrete Strategy 2: Bank Transfer
type BankTransferPayment struct {
BankName string
AccountNumber string
}
func (b *BankTransferPayment) Pay(amount float64) string {
return fmt.Sprintf("Membayar sejumlah $%.2f melalui Transfer Bank ke %s", amount, b.BankName)
}
ewallet_payment.go
:
package main
import "fmt"
// Concrete Strategy 3: E-Wallet
type EWalletPayment struct {
PhoneNumber string
}
func (e *EWalletPayment) Pay(amount float64) string {
return fmt.Sprintf("Membayar sejumlah $%.2f menggunakan E-Wallet (%s)", amount, e.PhoneNumber)
}
Langkah 3: Buat Context
Sekarang kita buat Context
-nya, yaitu Cart
. Cart
akan memiliki total belanja dan sebuah field untuk menyimpan strategi pembayaran yang dipilih.
cart.go
:
package main
import "fmt"
// Context
type Cart struct {
totalCost float64
paymentStrategy PaymentStrategy // Menyimpan referensi ke strategy
}
func NewCart(total float64) *Cart {
return &Cart{
totalCost: total,
}
}
// Method untuk mengubah strategy saat runtime
func (c *Cart) SetPaymentStrategy(strategy PaymentStrategy) {
c.paymentStrategy = strategy
}
// Method untuk menjalankan proses pembayaran
func (c *Cart) Checkout() {
if c.paymentStrategy == nil {
fmt.Println("Silakan pilih metode pembayaran terlebih dahulu.")
return
}
result := c.paymentStrategy.Pay(c.totalCost)
fmt.Println(result)
}
Langkah 4: Penggunaan di Client (main.go)
Terakhir, kita akan melihat bagaimana klien (dalam hal ini, fungsi main
) menggunakan semua komponen ini. Di sini kita akan menunjukkan fleksibilitas dalam mengubah strategi pembayaran saat runtime.
main.go
:
package main
import "fmt"
func main() {
// Buat sebuah keranjang belanja dengan total $150.50
cart := NewCart(150.50)
// Skenario 1: Pembayaran dengan Kartu Kredit
fmt.Println("--- Skenario 1: Pembayaran dengan Kartu Kredit ---")
creditCard := &CreditCardPayment{
CardNumber: "1234-5678-9012-3456",
Owner: "Budi Santoso",
}
cart.SetPaymentStrategy(creditCard)
cart.Checkout()
fmt.Println("\n-------------------------------------------------\n")
// Skenario 2: Pelanggan mengubah pikiran dan ingin membayar via Transfer Bank
fmt.Println("--- Skenario 2: Mengganti ke Transfer Bank ---")
bankTransfer := &BankTransferPayment{
BankName: "Bank ABC",
AccountNumber: "987654321",
}
cart.SetPaymentStrategy(bankTransfer)
cart.Checkout()
fmt.Println("\n-------------------------------------------------\n")
// Skenario 3: Pembayaran lain dengan E-Wallet
fmt.Println("--- Skenario 3: Pembayaran lain dengan E-Wallet ---")
ewallet := &EWalletPayment{
PhoneNumber: "08123456789",
}
// Bisa juga membuat keranjang baru atau menggunakan yang sama
cart.SetPaymentStrategy(ewallet)
cart.Checkout()
}
Output yang Dihasilkan:
--- Skenario 1: Pembayaran dengan Kartu Kredit ---
Membayar sejumlah $150.50 menggunakan Kartu Kredit 1234-5678-9012-3456
-------------------------------------------------
--- Skenario 2: Mengganti ke Transfer Bank ---
Membayar sejumlah $150.50 melalui Transfer Bank ke Bank ABC
-------------------------------------------------
--- Skenario 3: Pembayaran lain dengan E-Wallet ---
Membayar sejumlah $150.50 menggunakan E-Wallet (08123456789)
Keuntungan Menggunakan Strategy Pattern
Keterpisahan (Decoupling): Context terpisah dari implementasi konkret algoritma. Context hanya bergantung pada
interface
, bukan pada implementasi detailnya.Menghindari
if-else
/switch
yang Kompleks: Alih-alih memiliki blok kondisional yang besar di dalam Context untuk memilih perilaku, Anda cukup mendelegasikannya ke objek Strategy. Ini membuat kode Context lebih bersih dan sederhana.Sesuai dengan Open/Closed Principle: Anda dapat menambahkan strategi-strategi baru (misalnya, pembayaran dengan QRIS, cicilan, dll.) tanpa harus mengubah kode di dalam
Cart
(Context
). Anda hanya perlu membuat struct baru yang mengimplementasikanPaymentStrategy
.Fleksibilitas Runtime: Seperti yang ditunjukkan pada contoh, strategi dapat dengan mudah diganti pada saat program berjalan, memberikan fleksibilitas yang tinggi.
Kesimpulan
Strategy Pattern adalah alat yang sangat kuat dalam Go untuk merancang sistem yang fleksibel dan mudah dipelihara. Dengan memisahkan "apa yang dilakukan" (di dalam Context) dari "bagaimana melakukannya" (di dalam Concrete Strategies), Anda dapat membangun kode yang lebih bersih, lebih mudah diuji, dan lebih mudah untuk dikembangkan di masa depan. Pola ini sangat berguna dalam situasi di mana sebuah operasi dapat dilakukan dengan berbagai cara yang berbeda.
## Implementasi di Java
Kita akan menggunakan skenario yang sama seperti sebelumnya: sebuah sistem pembayaran untuk keranjang belanja (shopping cart) yang mendukung berbagai metode pembayaran.
### Langkah 1: Definisikan Interface Strategy
Pertama, kita buat interface
yang akan menjadi cetak biru untuk semua strategi pembayaran.
PaymentStrategy.java
// 1. Interface Strategy
public interface PaymentStrategy {
public void pay(int amount);
}
### Langkah 2: Buat Kelas Concrete Strategy
Sekarang, kita buat beberapa kelas yang mengimplementasikan interface
di atas. Masing-masing mewakili satu metode pembayaran.
CreditCardPayment.java
// 2. Concrete Strategy untuk Kartu Kredit
public class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
public CreditCardPayment(String name, String cardNumber) {
this.name = name;
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " dibayar dengan kartu kredit.");
System.out.println("Detail Kartu: " + this.name + ", " + this.cardNumber);
}
}
EWalletPayment.java
// 2. Concrete Strategy untuk E-Wallet
public class EWalletPayment implements PaymentStrategy {
private String phoneNumber;
public EWalletPayment(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " dibayar menggunakan E-Wallet.");
System.out.println("Akun E-Wallet: " + this.phoneNumber);
}
}
### Langkah 3: Buat Kelas Context
Kelas Context
adalah objek yang perilakunya ingin kita ubah. Dalam kasus ini, ShoppingCart
. Kelas ini akan memiliki sebuah field untuk menyimpan strategi pembayaran yang sedang aktif.
ShoppingCart.java
// 3. Context
public class ShoppingCart {
private int amount;
// Context memegang referensi ke salah satu Concrete Strategy.
// Context tidak tahu kelas konkret dari strategi tersebut.
private PaymentStrategy paymentStrategy;
public ShoppingCart(int amount) {
this.amount = amount;
}
// Setter untuk mengubah strategi saat runtime
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout() {
if (paymentStrategy == null) {
System.out.println("Metode pembayaran belum dipilih.");
return;
}
// Context mendelegasikan pekerjaan ke objek Strategy
paymentStrategy.pay(this.amount);
}
}
### Langkah 4: Penggunaan di Client (Kelas Main)
Terakhir, kita buat kelas Main
untuk mendemonstrasikan cara kerja pola ini. Di sini kita akan membuat ShoppingCart
dan mengganti-ganti strategi pembayarannya.
Main.java
public class Main {
public static void main(String[] args) {
// Buat keranjang belanja dengan total 1200
ShoppingCart cart = new ShoppingCart(1200);
// Skenario 1: Pembayaran dengan Kartu Kredit
System.out.println("--- Skenario 1: Bayar dengan Kartu Kredit ---");
PaymentStrategy creditCard = new CreditCardPayment("Budi Santoso", "1234-5678-9012-3456");
// Atur strategi dan lakukan checkout
cart.setPaymentStrategy(creditCard);
cart.checkout();
System.out.println("\n--------------------------------------------\n");
// Skenario 2: Pelanggan berubah pikiran, bayar dengan E-Wallet
System.out.println("--- Skenario 2: Ganti metode ke E-Wallet ---");
PaymentStrategy eWallet = new EWalletPayment("081234567890");
// Ganti strategi di objek yang sama dan lakukan checkout lagi
cart.setPaymentStrategy(eWallet);
cart.checkout();
}
}
### Output Program
--- Skenario 1: Bayar dengan Kartu Kredit ---
1200 dibayar dengan kartu kredit.
Detail Kartu: Budi Santoso, 1234-5678-9012-3456
--------------------------------------------
--- Skenario 2: Ganti metode ke E-Wallet ---
1200 dibayar menggunakan E-Wallet.
Akun E-Wallet: 081234567890
## Keuntungan Menggunakan Strategy Pattern
Menghindari
if-else
atauswitch
: Pola ini adalah alternatif yang elegan untuk blok kondisional yang besar saat memilih perilaku.Prinsip Terbuka/Tertutup (Open/Closed Principle): Anda bisa menambahkan strategi baru tanpa mengubah kode kelas
Context
atau strategi yang sudah ada. Cukup buat kelas baru yang mengimplementasikaninterface
PaymentStrategy
.Enkapsulasi: Detail implementasi dari setiap algoritma terisolasi dalam kelasnya sendiri, membuatnya lebih mudah dipahami dan dikelola.
Fleksibilitas Runtime: Strategi dapat diganti saat aplikasi sedang berjalan, memberikan kontrol dinamis atas perilaku objek.
Kode Lebih Bersih dan Mudah Diuji: Setiap strategi dapat diuji secara terpisah, dan kelas
Context
menjadi lebih sederhana karena tidak lagi berisi logika bisnis yang kompleks.
Last updated