State vs Strategy Pattern: Perbedaan dan Kapan Menggunakannya
Pendahuluan
Pernahkah kamu kebingungan memilih antara State Pattern dan Strategy Pattern? Keduanya terlihat sangat mirip di permukaan — sama-sama melibatkan objek yang memiliki “perilaku berbeda” dan sama-sama menggunakan komposisi. Namun, di balik kemiripannya, ada perbedaan filosofis yang cukup mendasar yang menentukan kapan kamu harus memilih salah satunya.
Artikel ini akan membedah keduanya secara mendalam, lengkap dengan contoh kode yang bisa langsung kamu jalankan. Jika kamu sudah familiar dengan konsep OOP di Python: Class, Object, dan Inheritance, kamu sudah punya modal yang baik untuk memahami kedua pola ini.
Membedah State Pattern
State Pattern adalah pola desain behavioral yang memungkinkan sebuah objek mengubah perilakunya sendiri ketika kondisi internalnya berubah. Objek tersebut seolah-olah berganti kelas.
Prinsip inti: Objek memiliki state (kondisi), dan state itulah yang menentukan perilakunya. State bisa bertransisi ke state lain berdasarkan aksi yang terjadi.
Bayangkan kamu membangun fitur order management seperti di aplikasi e-commerce. Sebuah pesanan bisa berada dalam kondisi: Pending, Processing, Shipped, Delivered, atau Cancelled. Setiap kondisi memiliki aturan transisi yang berbeda — pesanan yang sudah Delivered tidak bisa dikembalikan ke Processing.
from abc import ABC, abstractmethod
# Interface State
class OrderState(ABC):
@abstractmethod
def next(self, order):
pass
@abstractmethod
def cancel(self, order):
pass
@abstractmethod
def info(self):
pass
# Konkret State: Pending
class PendingState(OrderState):
def next(self, order):
print("Pesanan diproses oleh seller...")
order.set_state(ProcessingState())
def cancel(self, order):
print("Pesanan dibatalkan dari state Pending.")
order.set_state(CancelledState())
def info(self):
return "Status: PENDING"
# Konkret State: Processing
class ProcessingState(OrderState):
def next(self, order):
print("Pesanan dikirim ke kurir...")
order.set_state(ShippedState())
def cancel(self, order):
print("Tidak bisa dibatalkan, pesanan sudah diproses.")
def info(self):
return "Status: PROCESSING"
# Konkret State: Shipped
class ShippedState(OrderState):
def next(self, order):
print("Pesanan telah diterima pembeli!")
order.set_state(DeliveredState())
def cancel(self, order):
print("Tidak bisa dibatalkan, pesanan sudah dikirim.")
def info(self):
return "Status: SHIPPED"
# Konkret State: Delivered
class DeliveredState(OrderState):
def next(self, order):
print("Pesanan sudah selesai, tidak ada aksi selanjutnya.")
def cancel(self, order):
print("Pesanan sudah diterima, tidak bisa dibatalkan.")
def info(self):
return "Status: DELIVERED"
# Konkret State: Cancelled
class CancelledState(OrderState):
def next(self, order):
print("Pesanan sudah dibatalkan, tidak ada aksi selanjutnya.")
def cancel(self, order):
print("Pesanan sudah dibatalkan.")
def info(self):
return "Status: CANCELLED"
# Context
class Order:
def __init__(self):
self._state = PendingState()
def set_state(self, state: OrderState):
self._state = state
def next(self):
self._state.next(self)
def cancel(self):
self._state.cancel(self)
def info(self):
print(self._state.info())
# Penggunaan
order = Order()
order.info() # Status: PENDING
order.next() # Pesanan diproses oleh seller...
order.info() # Status: PROCESSING
order.next() # Pesanan dikirim ke kurir...
order.info() # Status: SHIPPED
order.cancel() # Tidak bisa dibatalkan, pesanan sudah dikirim.
order.next() # Pesanan telah diterima pembeli!
order.info() # Status: DELIVERED
Perhatikan bahwa state tahu cara bertransisi ke state berikutnya. Inilah ciri khas State Pattern.
Membedah Strategy Pattern
Strategy Pattern adalah pola desain behavioral yang mendefinisikan sekumpulan algoritma, mengenkapsulasi masing-masing, dan membuatnya bisa dipertukarkan. Strategy memungkinkan algoritma bervariasi secara independen dari klien yang menggunakannya.
Prinsip inti: Objek memiliki satu tugas, tetapi cara menjalankan tugas itu bisa dipilih dan diganti dari luar. Strategy tidak tahu apa-apa tentang strategy lain.
Contoh klasik: sistem pembayaran. Kamu ingin mendukung berbagai metode pembayaran (OVO, GoPay, transfer bank) tanpa mengubah logika utama checkout.
from abc import ABC, abstractmethod
# Interface Strategy
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float):
pass
# Konkret Strategy: OVO
class OVOPayment(PaymentStrategy):
def __init__(self, phone_number: str):
self.phone_number = phone_number
def pay(self, amount: float):
print(f"Membayar Rp{amount:,.0f} menggunakan OVO ({self.phone_number})")
# Konkret Strategy: GoPay
class GopayPayment(PaymentStrategy):
def __init__(self, account_id: str):
self.account_id = account_id
def pay(self, amount: float):
print(f"Membayar Rp{amount:,.0f} menggunakan GoPay (ID: {self.account_id})")
# Konkret Strategy: Transfer Bank
class BankTransferPayment(PaymentStrategy):
def __init__(self, bank_name: str, account_number: str):
self.bank_name = bank_name
self.account_number = account_number
def pay(self, amount: float):
print(f"Transfer Rp{amount:,.0f} ke {self.bank_name} - {self.account_number}")
# Context
class ShoppingCart:
def __init__(self):
self._items = []
self._payment_strategy: PaymentStrategy = None
def add_item(self, name: str, price: float):
self._items.append({"name": name, "price": price})
def set_payment_strategy(self, strategy: PaymentStrategy):
self._payment_strategy = strategy
def checkout(self):
total = sum(item["price"] for item in self._items)
if not self._payment_strategy:
raise Exception("Pilih metode pembayaran terlebih dahulu!")
self._payment_strategy.pay(total)
# Penggunaan
cart = ShoppingCart()
cart.add_item("Laptop", 12_000_000)
cart.add_item("Mouse", 250_000)
# User memilih OVO
cart.set_payment_strategy(OVOPayment("0812-xxxx-xxxx"))
cart.checkout()
# Output: Membayar Rp12.250.000 menggunakan OVO (0812-xxxx-xxxx)
# User ganti ke GoPay
cart.set_payment_strategy(GopayPayment("gopay_user_123"))
cart.checkout()
# Output: Membayar Rp12.250.000 menggunakan GoPay (ID: gopay_user_123)
# User ganti ke Transfer Bank
cart.set_payment_strategy(BankTransferPayment("BCA", "1234567890"))
cart.checkout()
# Output: Transfer Rp12.250.000 ke BCA - 1234567890
Perhatikan bahwa strategy tidak tahu state objek lain dan tidak memicu pergantian strategy secara otomatis. Strategi diganti dari luar oleh klien.
Perbandingan Head-to-Head: State vs. Strategy
| Aspek | State Pattern | Strategy Pattern |
|---|---|---|
| Tujuan | Mengelola transisi kondisi internal | Memilih algoritma yang bisa dipertukarkan |
| Siapa yang mengubah? | State itu sendiri (atau Context) | Klien dari luar |
| State/Strategy tahu satu sama lain? | Ya — state tahu state berikutnya | Tidak — strategy independen |
| Jumlah “kondisi”? | Banyak kondisi yang saling terkait | Banyak cara berbeda untuk satu tugas |
| Pertanyaan utama | ”Objek ini sedang dalam kondisi apa?" | "Cara mana yang dipilih untuk melakukan ini?” |
| Analogi | Lampu lalu lintas (merah → kuning → hijau) | Pilihan rute navigasi (tol, jalan biasa, tol+jalan biasa) |
Cara mudah membedakan:
- Jika objekmu punya siklus hidup dan perilaku bergantung pada di mana objek berada dalam siklus tersebut → gunakan State Pattern.
- Jika kamu ingin menukar algoritma atau strategi eksekusi tanpa mengubah objek utama → gunakan Strategy Pattern.
Contoh Kasus Nyata
Kasus 1: Koneksi Database dengan State Pattern
Jika kamu membangun backend yang mirip arsitektur microservice, koneksi database bisa memiliki state: Disconnected → Connecting → Connected → Error.
from abc import ABC, abstractmethod
class ConnectionState(ABC):
@abstractmethod
def connect(self, db):
pass
@abstractmethod
def query(self, db, sql: str):
pass
@abstractmethod
def disconnect(self, db):
pass
@abstractmethod
def status(self) -> str:
pass
class DisconnectedState(ConnectionState):
def connect(self, db):
print("Menghubungkan ke database...")
db.set_state(ConnectedState())
def query(self, db, sql: str):
print("Tidak bisa query, koneksi belum dibuka.")
def disconnect(self, db):
print("Koneksi sudah terputus.")
def status(self) -> str:
return "DISCONNECTED"
class ConnectedState(ConnectionState):
def connect(self, db):
print("Sudah terhubung ke database.")
def query(self, db, sql: str):
print(f"Menjalankan query: {sql}")
def disconnect(self, db):
print("Memutus koneksi database.")
db.set_state(DisconnectedState())
def status(self) -> str:
return "CONNECTED"
class DBConnection:
def __init__(self):
self._state = DisconnectedState()
def set_state(self, state: ConnectionState):
self._state = state
def connect(self):
self._state.connect(self)
def query(self, sql: str):
self._state.query(self, sql)
def disconnect(self):
self._state.disconnect(self)
def status(self):
print(f"Status koneksi: {self._state.status()}")
# Penggunaan
db = DBConnection()
db.status() # Status koneksi: DISCONNECTED
db.query("SELECT * FROM users") # Tidak bisa query, koneksi belum dibuka.
db.connect() # Menghubungkan ke database...
db.status() # Status koneksi: CONNECTED
db.query("SELECT * FROM users") # Menjalankan query: SELECT * FROM users
db.disconnect() # Memutus koneksi database.
db.status() # Status koneksi: DISCONNECTED
Kasus 2: Sorting dengan Strategy Pattern
Prinsip yang sama digunakan saat memilih algoritma sorting. Algoritma mana yang dipilih tergantung konteks dan ukuran data, bukan kondisi internal objeknya. Konsep ini juga berkaitan dengan Memahami Design Pattern dalam Pemrograman.
// Concrete Strategies
const bubbleSort = (arr) => {
const result = [...arr];
for (let i = 0; i < result.length; i++) {
for (let j = 0; j < result.length - i - 1; j++) {
if (result[j] > result[j + 1]) {
[result[j], result[j + 1]] = [result[j + 1], result[j]];
}
}
}
return result;
};
const quickSort = (arr) => {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return [...quickSort(left), ...middle, ...quickSort(right)];
};
const mergeSort = (arr) => {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
const merged = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) merged.push(left[i++]);
else merged.push(right[j++]);
}
return [...merged, ...left.slice(i), ...right.slice(j)];
};
// Context
class DataSorter {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
sort(data) {
return this.strategy(data);
}
}
const data = [64, 25, 12, 22, 11];
const sorter = new DataSorter(bubbleSort);
console.log("Bubble Sort:", sorter.sort(data)); // [11, 12, 22, 25, 64]
sorter.setStrategy(quickSort);
console.log("Quick Sort:", sorter.sort(data)); // [11, 12, 22, 25, 64]
sorter.setStrategy(mergeSort);
console.log("Merge Sort:", sorter.sort(data)); // [11, 12, 22, 25, 64]
Di sini, DataSorter tidak peduli bagaimana data diurutkan — itu urusan strategy. Klien yang memilih strategi mana yang dipakai berdasarkan ukuran data atau kebutuhan performa.
Pertanyaan yang Sering Diajukan
Apakah State Pattern dan Strategy Pattern bisa digunakan bersamaan?
Ya, keduanya bisa dikombinasikan. Misalnya, kamu bisa menggunakan State Pattern untuk mengelola siklus hidup pesanan, lalu di dalam masing-masing state, kamu menggunakan Strategy Pattern untuk menentukan cara menghitung ongkos kirim. Keduanya saling melengkapi tanpa konflik.
Bagaimana cara menentukan apakah sebuah masalah cocok untuk State Pattern atau Strategy Pattern?
Tanyakan pada dirimu sendiri: “Apakah pergantian perilaku terjadi secara otomatis berdasarkan kondisi internal objek?” Jika ya, gunakan State Pattern. “Apakah pengguna atau sistem luar yang memilih cara bekerja?” Jika ya, gunakan Strategy Pattern. Kunci utamanya adalah siapa yang memicu perubahan perilaku.
Mengapa tidak cukup menggunakan if-else biasa dibanding kedua pattern ini?
Untuk kasus sederhana, if-else memang cukup. Namun saat kondisi bertambah banyak, kode if-else yang panjang menjadi sulit dipelihara dan rentan bug saat ada perubahan. Dengan State/Strategy Pattern, setiap kondisi atau algoritma dikapsulasi dalam class tersendiri — lebih mudah ditambah, diuji, dan dipahami. Ini adalah penerapan prinsip Open/Closed dari SOLID.
Apakah Strategy Pattern sama dengan dependency injection?
Keduanya mirip dalam konsep “menyuntikkan perilaku dari luar,” tetapi berbeda tujuan. Dependency Injection adalah teknik untuk menyediakan dependensi ke suatu objek, sementara Strategy Pattern adalah pola untuk memilih di antara beberapa algoritma yang setara. Strategy Pattern justru sering diimplementasikan menggunakan dependency injection.
Bisakah state pada State Pattern disimpan di database?
Tentu bisa. Dalam aplikasi nyata seperti sistem manajemen pesanan, state biasanya disimpan di kolom database (misalnya kolom status). Saat objek dimuat dari database, kamu merestorasi state yang sesuai berdasarkan nilai kolom tersebut. Ini umum dilakukan di framework seperti Django atau Laravel dengan pattern Repository.
Kesimpulan
State Pattern dan Strategy Pattern adalah dua alat berbeda untuk masalah yang berbeda. State Pattern tepat digunakan saat objek punya siklus hidup dengan transisi kondisi yang jelas — seperti status pesanan, koneksi jaringan, atau alur autentikasi. Strategy Pattern tepat digunakan saat kamu ingin menyediakan beberapa cara berbeda untuk melakukan satu tugas, dan pengguna atau sistem luar yang memilihnya — seperti metode pembayaran, algoritma sorting, atau strategi kompresi data.
Ingat satu aturan mudah: jika pergantian perilaku didorong dari dalam objek → State. Jika dipilih dari luar → Strategy.
Selamat bereksperimen dan terus tingkatkan kualitas kode kamu! Jika kamu ingin memperdalam pemahaman OOP dan design pattern secara keseluruhan, jangan lupa eksplorasi artikel-artikel lainnya di KamusNgoding — masih banyak pola dan konsep menarik yang menunggumu!