Langsung ke konten
KamusNgoding
Pemula Structural 5 menit baca

Apa itu Decorator Pattern? Penjelasan Lengkap untuk Pemula

#design patterns #structural #decorator #pemula #javascript

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:

  • EmailNotifier
  • SMSNotifier
  • EmailAndSMSNotifier
  • EmailAndWhatsAppNotifier
  • EmailSMSAndWhatsAppNotifier
  • …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:

KomponenPeran
ComponentInterface atau abstract class dasar
ConcreteComponentImplementasi dasar yang akan didekorasi
DecoratorAbstract class yang membungkus Component
ConcreteDecoratorMenambahkan 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!

Artikel Terkait