Strategy Pattern #

Dalam pengembangan perangkat lunak, sering kali kita menghadapi situasi di mana sebuah objek perlu menjalankan algoritma atau perilaku yang berbeda tergantung pada kondisi tertentu. Jika kita menuliskan semua logika tersebut menggunakan banyak if-else atau switch, kode akan cepat menjadi sulit dibaca dan sulit dirawat.

Strategy Pattern hadir sebagai solusi untuk masalah ini. Pattern ini memungkinkan kita untuk mendefinisikan sekumpulan algoritma, membungkus masing-masing algoritma ke dalam objek terpisah, dan membuatnya dapat diganti-ganti dengan mudah saat runtime tanpa mengubah kode klien yang menggunakan algoritma tersebut.

Dengan kata lain, Strategy Pattern memisahkan perilaku yang bisa berubah dari objek yang tetap, sehingga kode menjadi lebih fleksibel, mudah diperluas, dan lebih mudah diuji.

Apa itu Strategy Pattern? #

Strategy Pattern adalah salah satu behavioral design pattern yang bertujuan untuk mendefinisikan sekumpulan algoritma (strategy), membungkus masing-masing algoritma tersebut ke dalam struktur terpisah, dan membuatnya dapat dipertukarkan (interchangeable) tanpa mengubah kode klien yang menggunakannya.

Dengan Strategy Pattern, perilaku (algoritma) tidak ditanam langsung di dalam sebuah objek, melainkan didelegasikan ke objek lain yang merepresentasikan strategi tertentu.

Secara sederhana:

“Encapsulate what varies, keep what stays the same.”


Tujuan dari Pattern #

Strategy Pattern digunakan untuk:

  1. Menghilangkan conditional kompleks (if-else / switch) yang bergantung pada jenis perilaku.
  2. Meningkatkan fleksibilitas, karena algoritma dapat diganti saat runtime.
  3. Menerapkan Open/Closed Principle (OCP) — menambah strategi baru tanpa mengubah kode yang sudah ada.
  4. Memisahkan business logic dari algoritma spesifik, sehingga kode lebih bersih dan mudah diuji.

Kapan Cocok Digunakan? #

Gunakan Strategy Pattern ketika:

  • Terdapat banyak variasi algoritma untuk satu tujuan yang sama.

  • Pemilihan algoritma bergantung pada konfigurasi, input, atau kondisi runtime.

  • Anda menemukan kode seperti:

    if type == "A" {
        // algoritma A
    } else if type == "B" {
        // algoritma B
    } else if type == "C" {
        // algoritma C
    }
    
  • Anda ingin mengganti perilaku tanpa mengubah objek utama.

  • Algoritma sering berubah, tetapi struktur objek tetap.

Contoh kasus nyata:

  • Metode pembayaran (Credit Card, Bank Transfer, E-Wallet)
  • Algoritma diskon
  • Strategi pengiriman (JNE, J&T, GoSend)
  • Algoritma kompresi / enkripsi
  • Perhitungan pajak berdasarkan negara

Struktur Pattern #

Komponen utama:

  1. Strategy (Interface)

    • Mendefinisikan kontrak algoritma
  2. ConcreteStrategy

    • Implementasi algoritma spesifik
  3. Context

    • Menggunakan Strategy dan mendelegasikan eksekusi algoritma

Contoh Implementasi (Golang) #

Studi Kasus: Metode Pembayaran #

Kita akan membuat sistem pembayaran dengan beberapa metode pembayaran berbeda.

Strategy Interface #

package payment

// Strategy
type PaymentStrategy interface {
    Pay(amount int) error
}

Concrete Strategies #

Credit Card #

package payment

import "fmt"

type CreditCardPayment struct {
    CardNumber string
}

func (c *CreditCardPayment) Pay(amount int) error {
    fmt.Printf("Paid %d using Credit Card (%s)\n", amount, c.CardNumber)
    return nil
}

Bank Transfer #

package payment

import "fmt"

type BankTransferPayment struct {
    BankName string
}

func (b *BankTransferPayment) Pay(amount int) error {
    fmt.Printf("Paid %d using Bank Transfer (%s)\n", amount, b.BankName)
    return nil
}

E-Wallet #

package payment

import "fmt"

type EWalletPayment struct {
    Provider string
}

func (e *EWalletPayment) Pay(amount int) error {
    fmt.Printf("Paid %d using E-Wallet (%s)\n", amount, e.Provider)
    return nil
}

Context #

package payment

// Context
type PaymentContext struct {
    strategy PaymentStrategy
}

func NewPaymentContext(strategy PaymentStrategy) *PaymentContext {
    return &PaymentContext{strategy: strategy}
}

func (p *PaymentContext) SetStrategy(strategy PaymentStrategy) {
    p.strategy = strategy
}

func (p *PaymentContext) Pay(amount int) error {
    if p.strategy == nil {
        return fmt.Errorf("payment strategy is not set")
    }
    return p.strategy.Pay(amount)
}

Penggunaan #

package main

import "yourapp/payment"

func main() {
    ctx := payment.NewPaymentContext(&payment.CreditCardPayment{
        CardNumber: "4111-1111-1111-1111",
    })

    ctx.Pay(100_000)

    // Ganti strategi saat runtime
    ctx.SetStrategy(&payment.BankTransferPayment{
        BankName: "BCA",
    })

    ctx.Pay(200_000)
}

Kelebihan dan Kekurangan #

✅ Kelebihan #

  • Mengurangi kompleksitas if-else
  • Mendukung OCP
  • Mudah diuji
  • Fleksibel dan extensible

❌ Kekurangan #

  • Menambah jumlah struct dan file
  • Bisa terasa berlebihan untuk kasus sederhana

Best Practices #

1. Gunakan Interface yang Kecil dan Spesifik #

Hindari interface gemuk:

// ❌ Buruk
type Strategy interface {
    Pay()
    Refund()
    Validate()
}

Lebih baik:

// ✅ Baik
type PaymentStrategy interface {
    Pay(amount int) error
}

2. Jangan Overengineering #

Jika hanya ada 1–2 algoritma dan kecil kemungkinan bertambah, Strategy Pattern bisa jadi berlebihan.

Rule of thumb:

Gunakan Strategy ketika variasi algoritma adalah bagian alami dari domain.

3. Kombinasikan dengan Factory Pattern #

Pemilihan strategy sering dipasangkan dengan Factory:

func PaymentStrategyFactory(method string) PaymentStrategy {
    switch method {
    case "credit_card":
        return &CreditCardPayment{}
    case "bank_transfer":
        return &BankTransferPayment{}
    default:
        return nil
    }
}

Strategy = perilaku Factory = pemilih perilaku

4. Strategy Harus Stateless (Jika Memungkinkan) #

Strategy yang stateless:

  • Lebih mudah diuji
  • Aman untuk reuse
  • Cocok untuk dependency injection

5. Testing Jadi Lebih Mudah #

func TestPayment(t *testing.T) {
    mockStrategy := &MockPaymentStrategy{}
    ctx := NewPaymentContext(mockStrategy)

    err := ctx.Pay(1000)
    assert.NoError(t, err)
}

Strategy Pattern sangat test-friendly.


Penutup #

Strategy Pattern adalah solusi elegan untuk menangani variasi algoritma tanpa membuat kode menjadi kaku dan sulit dirawat. Dalam Golang, pattern ini sangat natural karena dukungan interface yang ringan dan eksplisit.

Jika Anda sering menemukan banyak percabangan logika berdasarkan jenis perilaku, besar kemungkinan Strategy Pattern adalah jawaban yang tepat.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact