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:

  1. Naik mobil pribadi (Strategi A)

  2. Naik bus umum (Strategi B)

  3. 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:

  1. 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.

  2. 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.

  3. 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 atau switch 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

  1. Keterpisahan (Decoupling): Context terpisah dari implementasi konkret algoritma. Context hanya bergantung pada interface, bukan pada implementasi detailnya.

  2. 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.

  3. 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 mengimplementasikan PaymentStrategy.

  4. 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 atau switch: 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 mengimplementasikan interface 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