Tutorial Command Pattern untuk Pemula
Pendahuluan
Pernahkah kamu menggunakan tombol undo di aplikasi teks editor? Kamu mengetik sesuatu, lalu tekan Ctrl+Z, dan tulisanmu kembali seperti sebelumnya. Atau bayangkan remote control TV — satu tombol menjalankan satu perintah, dan kamu bisa menekannya kapan saja tanpa perlu tahu bagaimana TV itu bekerja di dalamnya.
Itulah inti dari Command Pattern — sebuah behavioral design pattern yang membungkus sebuah perintah (request) menjadi sebuah objek tersendiri. Dengan cara ini, perintah bisa disimpan, diantri, dieksekusi, bahkan dibatalkan.
Jika kamu sudah mengenal konsep Mengenal Design Patterns: Fondasi Arsitektur Software, Command Pattern adalah salah satu pola yang paling praktis untuk dipelajari karena langsung terasa manfaatnya di kehidupan nyata sebagai developer.
Memahami Komponen Utama Command Pattern
Command Pattern memiliki empat komponen utama. Mari kita pahami satu per satu dengan analogi sederhana.
Bayangkan kamu memesan makanan di restoran:
- Client → Kamu (pelanggan yang memutuskan mau pesan apa)
- Invoker → Pelayan yang menerima pesanan dan menyampaikannya ke dapur
- Command → Nota/slip pesanan yang berisi instruksi spesifik
- Receiver → Dapur (yang benar-benar mengeksekusi masakan)
Client → membuat Command → menyerahkan ke Invoker → Invoker memanggil execute() → Receiver bekerja
Dalam kode, strukturnya seperti ini:
from abc import ABC, abstractmethod
# Interface Command
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Receiver — yang benar-benar melakukan pekerjaan
class Lampu:
def __init__(self, nama):
self.nama = nama
self.nyala = False
def nyalakan(self):
self.nyala = True
print(f"Lampu {self.nama} menyala!")
def matikan(self):
self.nyala = False
print(f"Lampu {self.nama} mati!")
# Concrete Command
class NyalakanLampu(Command):
def __init__(self, lampu: Lampu):
self.lampu = lampu
def execute(self):
self.lampu.nyalakan()
def undo(self):
self.lampu.matikan()
class MatikanLampu(Command):
def __init__(self, lampu: Lampu):
self.lampu = lampu
def execute(self):
self.lampu.matikan()
def undo(self):
self.lampu.nyalakan()
Implementasi Command Pattern: Studi Kasus Remote Control
Sekarang kita bangun remote control yang sesungguhnya — lengkap dengan fitur undo. Ini adalah contoh klasik yang memperlihatkan kekuatan nyata dari Command Pattern.
Kode berikut ini lengkap dan bisa langsung dijalankan. Salin seluruh blok ke file remote_control.py lalu jalankan dengan python remote_control.py.
from abc import ABC, abstractmethod
# ── Interface Command ──────────────────────────────────────────────────────────
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# ── Receiver ───────────────────────────────────────────────────────────────────
class Lampu:
def __init__(self, nama):
self.nama = nama
self.nyala = False
def nyalakan(self):
self.nyala = True
print(f"Lampu {self.nama} menyala!")
def matikan(self):
self.nyala = False
print(f"Lampu {self.nama} mati!")
# ── Concrete Commands ──────────────────────────────────────────────────────────
class NyalakanLampu(Command):
def __init__(self, lampu: Lampu):
self.lampu = lampu
def execute(self):
self.lampu.nyalakan()
def undo(self):
self.lampu.matikan()
class MatikanLampu(Command):
def __init__(self, lampu: Lampu):
self.lampu = lampu
def execute(self):
self.lampu.matikan()
def undo(self):
self.lampu.nyalakan()
# ── Invoker ────────────────────────────────────────────────────────────────────
class RemoteControl:
def __init__(self):
self.riwayat = [] # stack untuk menyimpan history command
def tekan_tombol(self, command: Command):
command.execute()
self.riwayat.append(command)
def tekan_undo(self):
if not self.riwayat:
print("Tidak ada aksi yang bisa di-undo.")
return
command_terakhir = self.riwayat.pop()
command_terakhir.undo()
print("Aksi terakhir berhasil di-undo!")
# ── Program Utama ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# Setup receiver
lampu_ruang_tamu = Lampu("Ruang Tamu")
lampu_kamar = Lampu("Kamar")
remote = RemoteControl()
# Buat command
cmd_nyala_ruang_tamu = NyalakanLampu(lampu_ruang_tamu)
cmd_nyala_kamar = NyalakanLampu(lampu_kamar)
cmd_mati_ruang_tamu = MatikanLampu(lampu_ruang_tamu)
# Gunakan remote
print("=== Menyalakan lampu ===")
remote.tekan_tombol(cmd_nyala_ruang_tamu)
remote.tekan_tombol(cmd_nyala_kamar)
remote.tekan_tombol(cmd_mati_ruang_tamu)
print("\n=== Undo dua aksi terakhir ===")
remote.tekan_undo()
remote.tekan_undo()
Output yang dihasilkan:
=== Menyalakan lampu ===
Lampu Ruang Tamu menyala!
Lampu Kamar menyala!
Lampu Ruang Tamu mati!
=== Undo dua aksi terakhir ===
Aksi terakhir berhasil di-undo!
Lampu Ruang Tamu menyala!
Aksi terakhir berhasil di-undo!
Lampu Kamar mati!
Perhatikan betapa rapihnnya kode ini. RemoteControl tidak tahu apa-apa tentang lampu — ia hanya tahu cara memanggil execute() dan undo(). Inilah keindahan Command Pattern.
Keuntungan Menggunakan Command Pattern
1. Fitur Undo/Redo Menjadi Mudah
Dengan menyimpan history command dalam sebuah stack, kamu bisa membangun fitur undo/redo dengan sangat elegan — persis seperti yang ada di Figma, Google Docs, atau aplikasi code editor.
2. Command Bisa Diantri (Queue)
Command adalah objek biasa. Artinya, kamu bisa menyimpannya di list, mengirimkannya lewat jaringan, atau memasukkannya ke dalam queue untuk diproses belakangan.
Ini sangat berguna untuk sistem background job. Jika kamu pernah belajar tentang Queue dan Jobs di Laravel: Panduan Lengkap Background Processing, konsep di baliknya sangat mirip — setiap job sebenarnya adalah sebuah command yang disimpan dan dieksekusi secara asinkron.
3. Mudah Ditambah Tanpa Merusak Kode Lama
Ingin menambah command baru? Cukup buat class baru yang implementasi interface Command. Kamu tidak perlu mengubah RemoteControl atau class lainnya sama sekali. Ini mengikuti prinsip Open/Closed Principle.
# Tambah command baru tanpa ubah kode yang sudah ada
class AktifkanAC(Command):
def __init__(self, ac):
self.ac = ac
def execute(self):
self.ac.hidupkan()
def undo(self):
self.ac.matikan()
4. Macro Command — Jalankan Banyak Perintah Sekaligus
class MacroCommand(Command):
def __init__(self, commands: list):
self.commands = commands
def execute(self):
for cmd in self.commands:
cmd.execute()
def undo(self):
for cmd in reversed(self.commands):
cmd.undo()
# Contoh: mode "Tidur" — matikan semua lampu sekaligus
lampu_ruang_tamu = Lampu("Ruang Tamu")
lampu_kamar = Lampu("Kamar")
remote = RemoteControl()
mode_tidur = MacroCommand([
MatikanLampu(lampu_ruang_tamu),
MatikanLampu(lampu_kamar),
])
remote.tekan_tombol(mode_tidur)
# Lampu Ruang Tamu mati!
# Lampu Kamar mati!
Contoh Kasus Nyata
Aplikasi Text Editor Sederhana
Contoh lengkap berikut menggabungkan semua komponen sehingga langsung bisa dijalankan:
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class RemoteControl:
def __init__(self):
self.riwayat = []
def tekan_tombol(self, command: Command):
command.execute()
self.riwayat.append(command)
def tekan_undo(self):
if not self.riwayat:
print("Tidak ada aksi yang bisa di-undo.")
return
self.riwayat.pop().undo()
class Editor:
def __init__(self):
self.teks = ""
def tambah_teks(self, teks):
self.teks += teks
def hapus_teks(self, jumlah_karakter):
self.teks = self.teks[:-jumlah_karakter]
def tampilkan(self):
print(f"Teks sekarang: '{self.teks}'")
class TambahTeks(Command):
def __init__(self, editor: Editor, teks: str):
self.editor = editor
self.teks = teks
def execute(self):
self.editor.tambah_teks(self.teks)
def undo(self):
self.editor.hapus_teks(len(self.teks))
if __name__ == "__main__":
editor = Editor()
history = RemoteControl()
history.tekan_tombol(TambahTeks(editor, "Halo, "))
editor.tampilkan() # Teks sekarang: 'Halo, '
history.tekan_tombol(TambahTeks(editor, "dunia!"))
editor.tampilkan() # Teks sekarang: 'Halo, dunia!'
history.tekan_undo()
editor.tampilkan() # Teks sekarang: 'Halo, '
Analogi di Dunia Nyata
Jika kamu ingin membangun aplikasi e-commerce seperti Tokopedia atau Shopee, Command Pattern sangat berguna untuk sistem pemrosesan pesanan. Setiap aksi — bayar, batalkan, kembalikan dana — bisa dibungkus sebagai command. Jika terjadi kegagalan di tengah proses, kamu bisa memanggil undo() untuk rollback secara otomatis.
class BayarPesanan(Command):
def __init__(self, pesanan_id, jumlah):
self.pesanan_id = pesanan_id
self.jumlah = jumlah
self.berhasil = False
def execute(self):
# proses pembayaran ke payment gateway
print(f"Memproses pembayaran Rp{self.jumlah:,} untuk pesanan #{self.pesanan_id}")
self.berhasil = True
def undo(self):
if self.berhasil:
print(f"Membatalkan pembayaran pesanan #{self.pesanan_id}, dana dikembalikan.")
self.berhasil = False
Pertanyaan yang Sering Diajukan
Apa perbedaan Command Pattern dengan Strategy Pattern?
Kedua pola ini sama-sama membungkus logika dalam objek, namun tujuannya berbeda. Strategy Pattern digunakan untuk mengganti algoritma atau cara melakukan sesuatu secara dinamis (misalnya, ganti metode sorting). Command Pattern fokus pada aksi atau permintaan yang bisa disimpan, diantri, dan di-undo. Strategy tentang “bagaimana”, Command tentang “apa yang harus dilakukan”.
Apakah Command Pattern cocok untuk project kecil?
Tidak selalu. Untuk fitur sederhana yang tidak membutuhkan undo/redo atau queue, Command Pattern bisa terasa berlebihan. Pola ini paling bersinar ketika kamu butuh: riwayat aksi yang bisa di-undo, eksekusi perintah yang ditunda, atau sistem logging setiap aksi yang terjadi.
Bagaimana cara menambahkan fitur Redo menggunakan Command Pattern?
Selain stack riwayat untuk undo, kamu perlu stack kedua, misalnya redo_stack. Ketika undo() dipanggil, pindahkan command dari riwayat ke redo_stack. Ketika redo() dipanggil, ambil kembali dari redo_stack, jalankan execute(), lalu kembalikan ke riwayat.
Apakah Command Pattern bisa digunakan di JavaScript?
Tentu! Prinsipnya sama, meskipun JavaScript tidak punya interface bawaan seperti Python atau Java. Kamu bisa menggunakan class biasa atau bahkan objek literal dengan method execute dan undo.
class NyalakanLampu {
constructor(lampu) {
this.lampu = lampu;
}
execute() { this.lampu.nyalakan(); }
undo() { this.lampu.matikan(); }
}
Apa itu Command yang “idempoten”?
Sebuah command disebut idempoten jika menjalankannya berkali-kali menghasilkan efek yang sama seperti dijalankan sekali. Contohnya, “set lampu menjadi menyala” adalah idempoten, sedangkan “tambah item ke keranjang” tidak — karena dijalankan dua kali akan menambah dua item.
Kesimpulan
Command Pattern adalah solusi elegan untuk masalah yang sangat umum: bagaimana memisahkan siapa yang meminta sesuatu dari siapa yang mengerjakannya. Dengan membungkus setiap perintah sebagai objek, kamu mendapatkan fleksibilitas luar biasa — mulai dari fitur undo/redo, antrian perintah, hingga macro yang menjalankan banyak aksi sekaligus.
Kunci utamanya sederhana:
- Command → objek yang merepresentasikan satu aksi
- Invoker → yang menyuruh command dijalankan
- Receiver → yang benar-benar melakukan pekerjaan
Selamat belajar dan terus bereksperimen! Jika kamu ingin memperdalam pemahaman tentang pola-pola desain lainnya, jangan ragu untuk menjelajahi artikel-artikel menarik lainnya di KamusNgoding — masih banyak ilmu seru yang menunggumu!