Apa itu Decorator Pattern? Penjelasan Lengkap untuk Pemula
Pendahuluan
Pernahkah kamu memesan kopi di kafe dan menambahkan topping satu per satu — susu, gula, whipped cream, karamel? Setiap tambahan mengubah rasanya tanpa mengganti cangkir kopinya. Inilah inti dari Decorator Pattern: menambahkan fungsionalitas baru ke sebuah objek secara dinamis, tanpa mengubah struktur aslinya.
Decorator Pattern adalah salah satu pola desain struktural yang sering digunakan di dunia nyata. Jika kamu sudah membaca tentang Implementasi Singleton Pattern: Menjaga Satu Instance Tunggal, kamu tahu bahwa design pattern adalah solusi teruji untuk masalah umum dalam pengembangan perangkat lunak. Decorator Pattern menjawab satu pertanyaan penting: Bagaimana kita menambah kemampuan objek tanpa merusak kode yang sudah ada?
Memahami Masalah: Kebutuhan Fleksibilitas Objek
Bayangkan kamu sedang membangun sistem notifikasi. Awalnya, aplikasimu hanya mengirim notifikasi lewat email. Sederhana sekali:
class EmailNotifier:
# Kelas ini bertugas mengirim notifikasi email
def send(self, message: str):
# Menampilkan pesan email ke layar
print(f"Email: {message}")
# Membuat objek dari class EmailNotifier
notifier = EmailNotifier()
# Memanggil method send() dengan pesan contoh
notifier.send("Halo, ini notifikasi email.")
# Output yang diharapkan:
# > Email: Halo, ini notifikasi email.
Lalu tim produk meminta fitur baru: notifikasi SMS. Kemudian WhatsApp. Lalu push notification. Kalau kamu terus membuat subclass baru untuk setiap kombinasi, kamu akan berakhir dengan puluhan kelas:
EmailNotifierSMSNotifierEmailAndSMSNotifierEmailAndWhatsAppNotifierEmailSMSAndWhatsAppNotifier- …dan seterusnya.
Ini disebut class explosion — jumlah kelas meledak tak terkendali. Setiap kali ada channel baru, kamu harus membuat kombinasi kelas baru. Ini tidak efisien dan sulit dikelola.
Solusinya? Decorator Pattern.
Apa itu Decorator Pattern?
Decorator Pattern adalah pola desain yang memungkinkan kamu membungkus objek dengan objek lain untuk menambahkan perilaku baru, seperti menambah lapisan tanpa mengubah isi aslinya.
Analogi sederhananya: bayangkan ponselmu. Kamu bisa menambahkan casing pelindung, screen protector, atau ring holder. Setiap aksesori menambah fungsi baru (perlindungan, kenyamanan) tanpa mengubah ponsel itu sendiri.
Komponen utama Decorator Pattern:
| Komponen | Peran |
|---|---|
| Component | Interface atau abstract class dasar |
| ConcreteComponent | Implementasi dasar yang akan didekorasi |
| Decorator | Abstract class yang membungkus Component |
| ConcreteDecorator | Menambahkan perilaku spesifik |
Implementasi Decorator Pattern: Contoh Kode
Mari kita implementasikan sistem notifikasi tadi menggunakan Decorator Pattern dengan Python:
from abc import ABC, abstractmethod
# 1. Component: interface dasar untuk semua jenis notifikasi
class Notifier(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
# 2. ConcreteComponent: notifikasi dasar, yaitu email
class EmailNotifier(Notifier):
def send(self, message: str) -> None:
print(f"[Email] Mengirim: {message}")
# 3. BaseDecorator: pembungkus dasar untuk menambah fitur notifikasi
class NotifierDecorator(Notifier):
def __init__(self, wrapped: Notifier):
self._wrapped = wrapped # simpan objek notifier yang dibungkus
def send(self, message: str) -> None:
self._wrapped.send(message) # jalankan notifikasi yang sudah ada
# 4. ConcreteDecorator: menambahkan notifikasi SMS
class SMSDecorator(NotifierDecorator):
def send(self, message: str) -> None:
super().send(message) # kirim lewat notifier sebelumnya dulu
print(f"[SMS] Mengirim: {message}")
# 5. ConcreteDecorator: menambahkan notifikasi WhatsApp
class WhatsAppDecorator(NotifierDecorator):
def send(self, message: str) -> None:
super().send(message) # kirim lewat notifier sebelumnya dulu
print(f"[WhatsApp] Mengirim: {message}")
# Fungsi utama agar kode rapi dan mudah dijalankan
def main() -> None:
# Hanya Email
notifier = EmailNotifier()
notifier.send("Pesanan kamu berhasil!")
print("---")
# Email + SMS
notifier = SMSDecorator(EmailNotifier())
notifier.send("Pesanan kamu berhasil!")
print("---")
# Email + SMS + WhatsApp
notifier = WhatsAppDecorator(SMSDecorator(EmailNotifier()))
notifier.send("Pesanan kamu berhasil!")
# Menjalankan program
if __name__ == "__main__":
main()
# Output yang diharapkan:
# > [Email] Mengirim: Pesanan kamu berhasil!
# > ---
# > [Email] Mengirim: Pesanan kamu berhasil!
# > [SMS] Mengirim: Pesanan kamu berhasil!
# > ---
# > [Email] Mengirim: Pesanan kamu berhasil!
# > [SMS] Mengirim: Pesanan kamu berhasil!
# > [WhatsApp] Mengirim: Pesanan kamu berhasil!
Output yang dihasilkan:
[Email] Mengirim: Pesanan kamu berhasil!
---
[Email] Mengirim: Pesanan kamu berhasil!
[SMS] Mengirim: Pesanan kamu berhasil!
---
[Email] Mengirim: Pesanan kamu berhasil!
[SMS] Mengirim: Pesanan kamu berhasil!
[WhatsApp] Mengirim: Pesanan kamu berhasil!
Perhatikan betapa mudahnya menggabungkan channel notifikasi. Kamu tinggal membungkus satu decorator di atas decorator lain — seperti menumpuk lapisan bawang.
Berikut implementasi yang sama menggunakan JavaScript, yang mungkin lebih familiar bagi web developer:
// # Kelas dasar: semua notifier harus punya method send()
class Notifier {
send(message) {
throw new Error("Method send() harus diimplementasikan");
}
}
// # Komponen utama: kirim notifikasi lewat email
class EmailNotifier extends Notifier {
send(message) {
console.log(`[Email] Mengirim: ${message}`);
}
}
// # Decorator dasar: menyimpan objek notifier yang dibungkus
class NotifierDecorator extends Notifier {
constructor(wrappedNotifier) {
super();
this.wrappedNotifier = wrappedNotifier;
}
send(message) {
this.wrappedNotifier.send(message);
}
}
// # Decorator SMS: menambahkan pengiriman SMS
class SMSDecorator extends NotifierDecorator {
send(message) {
super.send(message);
console.log(`[SMS] Mengirim: ${message}`);
}
}
// # Decorator Push: menambahkan push notification
class PushNotificationDecorator extends NotifierDecorator {
send(message) {
super.send(message);
console.log(`[Push] Mengirim: ${message}`);
}
}
// # Membuat notifier dengan beberapa lapisan decorator
const notifier = new PushNotificationDecorator(
new SMSDecorator(new EmailNotifier())
);
// # Menjalankan semua notifikasi
notifier.send("Flash sale dimulai!");
/*
# Output yang diharapkan:
# > [Email] Mengirim: Flash sale dimulai!
# > [SMS] Mengirim: Flash sale dimulai!
# > [Push] Mengirim: Flash sale dimulai!
*/
Kode ini sangat fleksibel — kamu bisa menambah channel baru kapan saja tanpa menyentuh kode yang sudah ada.
Contoh Kasus Nyata
1. Stream I/O di Java
Java menggunakan Decorator Pattern secara luas di paket java.io. Pernahkah kamu menulis kode seperti ini?
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
// Membuka file "data.txt" untuk dibaca
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
// Membaca baris pertama dari file
String line = reader.readLine();
// Menampilkan isi baris ke layar
System.out.println("Isi file: " + line);
} catch (IOException e) {
// Menampilkan pesan jika file gagal dibaca
System.out.println("Terjadi kesalahan: " + e.getMessage());
}
}
}
// # Output yang diharapkan:
// # > Isi file: Halo, dunia!
Itulah Decorator Pattern! FileInputStream adalah komponen dasar, InputStreamReader membungkusnya dengan kemampuan membaca karakter, dan BufferedReader menambahkan buffering untuk performa lebih baik.
2. Middleware di Web Framework
Jika kamu pernah membangun RESTful API — misalnya seperti yang dijelaskan di artikel Membangun RESTful API Sederhana dengan Go: Tutorial Step-by-Step — kamu pasti familiar dengan middleware. Middleware adalah Decorator Pattern dalam aksi nyata:
# Simulasi middleware sederhana dengan decorator
def logging_decorator(handler):
# Decorator ini menambahkan log sebelum dan sesudah handler dijalankan
def wrapper(request):
print(f"Request masuk: {request['path']}")
response = handler(request)
print(f"Response keluar: {response['status']}")
return response
return wrapper
def auth_decorator(handler):
# Decorator ini memeriksa apakah request punya token
def wrapper(request):
if not request.get("token"):
return {"status": 401, "body": "Unauthorized"}
return handler(request)
return wrapper
def my_handler(request):
# Handler utama yang mengembalikan response
return {"status": 200, "body": "Hello, World!"}
# Menggabungkan beberapa decorator ke handler
secured_handler = logging_decorator(auth_decorator(my_handler))
# Contoh data request
request = {
"path": "/api/data",
"token": "abc123"
}
# Menjalankan handler
result = secured_handler(request)
# Menampilkan hasil akhir response
print("Body response:", result["body"])
# Output yang diharapkan:
# > Request masuk: /api/data
# > Response keluar: 200
# > Body response: Hello, World!
3. Fitur Premium Layanan Digital
Bayangkan jika kamu ingin membangun aplikasi streaming atau platform belanja online — kamu bisa menggunakan Decorator Pattern untuk mengelola fitur berdasarkan tier pengguna:
class UserAccount:
def get_features(self):
# Fitur dasar yang dimiliki semua pengguna
return ["Browsing dasar", "Pencarian produk"]
class PremiumDecorator:
def __init__(self, account):
# Simpan objek akun yang akan "dibungkus"
self._account = account
def get_features(self):
# Ambil fitur dari akun sebelumnya
features = self._account.get_features().copy()
# Tambahkan fitur khusus Premium
features.append("Bebas iklan")
features.append("Download offline")
return features
class BusinessDecorator:
def __init__(self, account):
# Simpan objek akun yang akan "dibungkus"
self._account = account
def get_features(self):
# Ambil fitur dari akun sebelumnya
features = self._account.get_features().copy()
# Tambahkan fitur khusus Business
features.append("Analitik toko")
features.append("Priority support")
return features
# Pengguna biasa hanya memakai akun dasar
user = UserAccount()
print(user.get_features())
# Pengguna ini memakai akun dasar + Premium + Business
power_user = BusinessDecorator(PremiumDecorator(UserAccount()))
print(power_user.get_features())
# Output yang diharapkan:
# > ['Browsing dasar', 'Pencarian produk']
# > ['Browsing dasar', 'Pencarian produk', 'Bebas iklan', 'Download offline', 'Analitik toko', 'Priority support']
Troubleshooting: Error yang Sering Muncul
TypeError: send() missing 1 required positional argument
Penyebab: Decorator tidak memanggil super().__init__(wrapped) dengan benar, sehingga self._wrapped tidak terdefinisi saat send() dipanggil.
Solusi:
class Notifier:
def send(self, message):
print(f"Mengirim pesan dasar: {message}")
class NotifierDecorator(Notifier):
def __init__(self, wrapped):
# Simpan objek notifier yang dibungkus
self.wrapped = wrapped
def send(self, message):
# Teruskan pengiriman ke objek asli
self.wrapped.send(message)
class SMSDecorator(NotifierDecorator):
def __init__(self, wrapped):
# Wajib memanggil constructor parent agar self.wrapped terisi
super().__init__(wrapped)
def send(self, message):
# Jalankan perilaku asli terlebih dahulu
self.wrapped.send(message)
# Tambahkan fitur baru: kirim notifikasi SMS
print(f"Mengirim notifikasi SMS: {message}")
# Membuat objek notifier dasar
basic_notifier = Notifier()
# Membungkus notifier dasar dengan decorator SMS
sms_notifier = SMSDecorator(basic_notifier)
# Menjalankan program
sms_notifier.send("Halo, akun Anda berhasil dibuat!")
# Output yang diharapkan:
# > Mengirim pesan dasar: Halo, akun Anda berhasil dibuat!
# > Mengirim notifikasi SMS: Halo, akun Anda berhasil dibuat!
Decorator tidak menjalankan perilaku objek yang dibungkus
Penyebab: Lupa memanggil super().send(message) di dalam ConcreteDecorator, sehingga decorator menggantikan — bukan menambahkan — perilaku asli.
Solusi:
class Notifier:
def send(self, message: str):
# Kirim notifikasi dasar, misalnya email
print(f"[Email] Mengirim: {message}")
class NotifierDecorator(Notifier):
def __init__(self, wrapped: Notifier):
# Simpan objek notifier yang dibungkus
self.wrapped = wrapped
def send(self, message: str):
# Teruskan pengiriman ke objek yang dibungkus
self.wrapped.send(message)
# Benar ✅ — Email + SMS keduanya terkirim
class SMSDecorator(NotifierDecorator):
def send(self, message: str):
super().send(message) # Jalankan notifier yang dibungkus dulu
print(f"[SMS] Mengirim: {message}") # Lalu tambahkan perilaku baru
# Membuat notifier dasar
notifier = Notifier()
# Membungkus notifier dengan dekorator SMS
sms_notifier = SMSDecorator(notifier)
# Menjalankan pengiriman pesan
sms_notifier.send("Halo, pengguna!")
# Output yang diharapkan:
# > [Email] Mengirim: Halo, pengguna!
# > [SMS] Mengirim: Halo, pengguna!
Urutan Decorator menghasilkan output yang tidak sesuai ekspektasi
Penyebab: Decorator dieksekusi dari dalam ke luar, sehingga urutan pembungkusan memengaruhi urutan eksekusi.
Solusi:
class EmailNotifier:
def send(self, message):
# Notifier dasar: selalu mengirim email terlebih dahulu
print(f"Email: {message}")
class NotifierDecorator:
def __init__(self, wrapped_notifier):
# Menyimpan objek notifier yang dibungkus
self.wrapped_notifier = wrapped_notifier
def send(self, message):
# Meneruskan proses kirim ke notifier sebelumnya
self.wrapped_notifier.send(message)
class SMSDecorator(NotifierDecorator):
def send(self, message):
# Jalankan notifier sebelumnya lebih dulu
super().send(message)
# Lalu tambahkan pengiriman SMS
print(f"SMS: {message}")
class WhatsAppDecorator(NotifierDecorator):
def send(self, message):
# Jalankan notifier sebelumnya lebih dulu
super().send(message)
# Lalu tambahkan pengiriman WhatsApp
print(f"WhatsApp: {message}")
# Urutan A: WhatsApp paling luar, jadi dieksekusi terakhir
notifier_a = WhatsAppDecorator(SMSDecorator(EmailNotifier()))
notifier_a.send("Halo")
print() # Baris kosong agar output lebih mudah dibaca
# Urutan B: SMS paling luar, jadi dieksekusi terakhir
notifier_b = SMSDecorator(WhatsAppDecorator(EmailNotifier()))
notifier_b.send("Halo")
# Output yang diharapkan:
# > Email: Halo
# > SMS: Halo
# > WhatsApp: Halo
# >
# > Email: Halo
# > WhatsApp: Halo
# > SMS: Halo
Pertanyaan yang Sering Diajukan
Apa perbedaan Decorator Pattern dengan Inheritance (pewarisan)?
Inheritance bersifat statis — hubungan antara class ditentukan saat kompilasi dan tidak bisa diubah saat runtime. Decorator Pattern bersifat dinamis — kamu bisa menambah atau melepas kemampuan objek saat program berjalan. Decorator juga menghindari masalah class explosion yang terjadi ketika terlalu banyak kombinasi subclass dibuat.
Apakah Decorator Pattern sama dengan Python @decorator syntax?
Mirip, tapi tidak identik. Python @decorator adalah fitur bahasa (syntactic sugar) untuk membungkus fungsi. Decorator Pattern dalam OOP lebih formal: melibatkan interface, class pembungkus, dan objek yang dibungkus. Namun keduanya berbagi konsep inti yang sama — membungkus sesuatu untuk menambah perilaku baru.
Bagaimana cara memilih antara Decorator Pattern dan Strategy Pattern?
Gunakan Decorator ketika kamu ingin menambahkan atau menumpuk perilaku secara dinamis pada satu objek, seperti menambahkan lapisan keamanan atau logging. Gunakan Strategy ketika kamu ingin menukar satu algoritma atau perilaku utuh dengan yang lain, seperti memilih metode pembayaran yang berbeda. Decorator bersifat kumulatif, Strategy bersifat selektif.
Apakah Decorator Pattern bisa digunakan untuk menghapus fitur?
Secara teknis bisa, tapi bukan cara yang direkomendasikan. Decorator dirancang untuk menambah, bukan mengurangi. Jika kamu perlu mengontrol akses ke fitur tertentu (misalnya menyembunyikan fitur untuk pengguna tertentu), pertimbangkan menggunakan Pattern lain seperti Proxy Pattern yang lebih cocok untuk skenario tersebut.
Kapan sebaiknya tidak menggunakan Decorator Pattern?
Hindari Decorator Pattern jika struktur sistem sederhana dan tidak perlu fleksibilitas tinggi — terlalu banyak layer decorator bisa membuat kode sulit di-debug. Jika kamu hanya punya 2-3 kombinasi yang sudah pasti dan tidak akan berubah, inheritance biasa mungkin lebih mudah dipahami oleh tim.
Kesimpulan
Decorator Pattern adalah solusi elegan untuk masalah fleksibilitas objek. Alih-alih membuat puluhan subclass, kamu cukup membuat decorator yang bisa ditumpuk sesuai kebutuhan — seperti memilih topping kopi favoritmu. Pola ini membantu kode tetap bersih, mudah diperluas, dan mematuhi prinsip Open/Closed Principle: terbuka untuk ekstensi, tertutup untuk modifikasi.
Kunci utama yang perlu diingat:
- Decorator membungkus objek, bukan mewarisinya
- Selalu panggil
super()agar rantai decorator bekerja dengan benar - Urutan pembungkusan memengaruhi urutan eksekusi
Selamat belajar dan terus berlatih! Decorator Pattern adalah salah satu pola yang paling sering kamu temui di framework modern, jadi semakin cepat kamu memahaminya, semakin percaya diri kamu dalam membaca dan menulis kode berkualitas tinggi. Jangan ragu untuk mengeksplorasi artikel-artikel menarik lainnya di KamusNgoding!