Langsung ke konten
KamusNgoding
Menengah Creational 5 menit baca

Mengenal Design Patterns: Fondasi Arsitektur Plugin yang Scalable

#creational #intermediate #pengenalan

Pendahuluan

Pernahkah Anda merasa frustrasi saat harus mengubah kode di satu bagian, namun tiba-tiba muncul puluhan error di bagian lain yang tidak berhubungan? Atau mungkin Anda sedang membangun sebuah sistem, dan ketika ada kebutuhan fitur baru, Anda merasa harus merombak ulang seluruh arsitektur yang sudah ada? Jika iya, Anda sedang menghadapi masalah klasik dalam pengembangan perangkat lunak: tight coupling dan kode yang tidak fleksibel.

Dalam artikel pertama dari seri “Membangun Plugin System” ini, kita tidak akan langsung menulis kode yang kompleks. Sebaliknya, kita akan meletakkan fondasi pemikiran yang kuat dengan mempelajari Design Patterns. Design patterns bukan sekadar “cara menulis kode”, melainkan solusi teruji untuk masalah-masalah yang sering muncul dalam pengembangan software skala besar.

Tujuan utama kita dalam seri ini adalah membangun sebuah Plugin System yang extensible. Artinya, kita ingin membuat sebuah sistem utama di mana developer lain (seperti rekan tim Anda, misalnya Budi atau Siti) bisa menambahkan fitur baru (plugin) tanpa harus menyentuh atau mengubah kode inti (core) yang sudah berjalan. Untuk mencapai itu, kita akan memulai dengan memahami fondasi dari Creational Design Patterns.

Apa Itu Design Patterns?

Secara sederhana, Design Pattern adalah template atau pola solusi yang sudah terbukti secara empiris untuk menyelesaikan masalah pengembangan software yang berulang. Bayangkan Anda adalah seorang arsitek bangunan di Jakarta. Saat Anda ingin membangun pintu, Anda tidak perlu menciptakan konsep “engsel” dari nol. Anda sudah tahu ada pola standar bagaimana pintu dipasang pada kusen agar bisa dibuka-tugas. Begitu pula dalam pemrograman.

Design patterns dikelompokkan oleh Gang of Four (GoF) ke dalam tiga kategori utama:

  1. Creational Patterns: Berfokus pada mekanisme pembuatan objek. Intinya, bagaimana kita membuat objek tanpa harus mengekspos logika pembuatan yang rumit ke pengguna.
  2. Structural Patterns: Berfokus pada bagaimana kelas dan objek disusun untuk membentuk struktur yang lebih besar dan efisien.
  3. Behavioral Patterns: Berfokus pada komunikasi dan pembagian tanggung jawab antar objek.

Dalam konteks proyek Plugin System kita, kategori Creational akan menjadi aktor utama di tahap awal. Mengapa? Karena untuk membuat sistem yang bisa menerima plugin baru, kita harus memiliki cara yang cerdas untuk “menciptakan” (instantiate) objek plugin tersebut tanpa harus tahu secara spesah detail kelas apa yang akan muncul di masa depan.

Fokus pada Creational Design Patterns

Masalah utama yang diselesaikan oleh Creational Patterns adalah masalah instansiasi. Dalam pemrograman tingkat pemula, kita terbiasin menggunakan kata kunci new (di Java/JS) atau langsung memanggil kelas (di Python) secara eksplisit.

Contoh kode yang “buruk” (terlalu tightly coupled):

# Contoh kode yang tidak fleksibel
class PaymentService:
    def process_payment(self, method_type):
        if method_type == "Gopay":
            # Kita hardcode kelas Gopay di sini
            payment = GopayPayment()
        elif method_type == "TransferBCA":
            # Kita hardcode kelas TransferBCA di sini
            payment = BcaPayment()
        
        payment.pay()

class GopayPayment:
    def pay(self):
        print("Membayar menggunakan Gopay sebesar Rp 50.000")

class BcaPayment:
    def pay(self):
        print("Membayar menggunakan Transfer BCA sebesar Rp 50.000")

# Masalah: Jika Andi ingin menambah metode 'OVO', 
# dia harus mengubah kelas PaymentService. Ini melanggar Open/Closed Principle.

Pada kode di atas, PaymentService sangat bergantung pada kelas GopolasPayment dan BcaPayment. Jika ada penambahan metode pembayaran baru, kita harus mengubah kode di dalam PaymentService. Inilah yang ingin kita hindari.

Creational Patterns seperti Factory Method, Abstract Factory, Singleton, Builder, dan Prototype memberikan cara untuk menyembunyikan logika if-else yang panjang tersebut. Kita akan memindahkan tanggung jawab pembuatan objek ke sebuah entitas khusus, sehingga PaymentService tidak perlu tahu kelas apa yang sebenarnya sedang dibuat.

Mengapa Perlu Abstraksi dalam Pembuatan Objek?

Mari kita gunakan analogan lokal. Bayangkan Anda sedang menggunakan aplikasi Gojek. Saat Anda ingin memesan makanan, Anda tidak perlu tahu bagaimana sistem Gojek membuatkan driver untuk Anda. Anda hanya perlu menekan tombol “Order”. Di balik layar, ada sistem yang sangat kompleks yang memutuskan: “Apakah saya harus membuat objek DriverMotor atau DriverMobil?”.

Jika Anda sebagai developer menggunakan prinsip Creational Pattern, Anda akan membuat sebuah “pabrik” (Factory). Tugas pabrik ini adalah menerima permintaan, lalu memutuskan objek mana yang paling tepat untuk dibuat. Dengan cara ini, ketika Gojek ingin menambahkan layanan “GoCar Luxury”, mereka hanya perlu mendaftarkan kelas baru ke dalam pabrik tersebut, tanpa mengubah alur utama pemesanan makanan Anda.

Keuntungan utama yang kita dapatkan adalah:

  1. Decoupling: Mengurangi ketergantungan antar kelas.
  2. Maintainability: Kode lebih mudah dirawat karena perubahan di satu tempat tidak merusak tempat lain.
  3. Extensibility: Menambah fitur baru semudah menambahkan kelas baru ke dalam daftar registrasi.

Implementasi dalam Proyek: Plugin System (Langkah 1)

Sekarang, mari kita mulai implementasi tahap pertama proyek kita. Kita akan membuat kerangka dasar sebuah PluginManager yang menggunakan konsep Factory sederhana untuk menciptakan plugin. Kita akan menggunakan Python 3.10+.

Tujuan kita adalah membuat sistem di mana kita bisa mendaftarkan plugin baru tanpa mengubah kelas PluginManager.

from abc import ABC, abstractmethod

# 1. Interface/Abstract Base Class untuk Plugin
# Ini adalah kontrak yang harus dipatuhi oleh semua plugin
class BasePlugin(ABC):
    @abstractmethod
    def execute(self) -> None:
        pass

# 2. Implementasi Plugin Spesifik
class LoggerPlugin(BasePlugin):
    def execute(self) -> None:
        print("[LOG] Plugin Logger sedang berjalan: Mencatat aktivitas sistem...")

class AuthPlugin(BasePlugin):
    def execute(self) -> None:
        print("[AUTH] Plugin Auth sedang berjalan: Memvalidasi token pengguna...")

# ical: Plugin baru yang dibuat oleh developer lain (misal: Reza)
class AnalyticsPlugin(BasePlugin):
    def execute(self) -> None:
        print("[ANALYTICS] Plugin Analytics sedang berjalan: Mengirim data ke dashboard...")

# 3. Plugin Manager (Menggunakan konsep Factory sederhana)
class PluginManager:
    def __init__(self):
        # Dictionary untuk menyimpan referensi kelas plugin yang terdaftar
        self._plugins = {}

    def register_plugin(self, name: str, plugin_class: type[BasePlugin]):
        """Mendaftarkan plugin ke dalam sistem."""
        self._plugins[name] = plugin_class
        print(f"Plugin '{name}' berhasil didaftarkan.")

    def run_plugin(self, name: str):
        """Menjalankan plugin berdasarkan nama yang terdaftar."""
        if name in self._plugins:
            # Di sinilah 'instantiation' terjadi. 
            # Kita membuat objek dari class yang tersimpan di dictionary.
            plugin_instance = self._plugins[name]()
            plugin_instance.execute()
        else:
            print(f"Error: Plugin '{name}' tidak ditemukan!")

# --- Simulasi Penggunaan ---

if __name__ == "__main__":
    # Inisialisasi Manager
    manager = PluginManager()
    # Kita perlu sedikit memperbaiki dictionary di dalam class agar bisa diakses
    # (Dalam praktek nyata, kita gunakan self._plugins = {} di __init__)
    manager._plugins = {} 

    # Mendaftarkan plugin (Inisialisasi awal)
    manager.register_plugin("logger", LoggerPlugin)
    manager.register_plugin("auth", AuthPlugin)
    manager.register_plugin("analytics", AnalyticsPlugin)

    print("\n--- Memulai Eksekusi Plugin ---")
    
    # Menjalankan plugin yang ada
    manager.run_plugin("logger")
    manager.run_plugin("auth")
    
    # Mencoba menjalankan plugin yang tidak terdaftar
    manager.run_plugin("payment_gateway")

(Catatan: Dalam kode di atas, saya menyederhanakan inisialisasi dictionary untuk keperluan demonstrasi singkat).

Pada kode tersebut, perhatikan bahwa PluginManager tidak tahu apa itu LoggerPlugin atau AuthPlugin saat ia dibuat. Ia hanya tahu bahwa ia akan menerima sebuah class dan menyimpannya. Saat run_plugin dipanggil, ia melakukan instansiasi. Inilah inti dari pemisahan tanggung jawab.

Kesimpulan

Kita telah belajar bahwa:

  1. Creational Patterns berfokus pada bagaimana objek dibuat.
  2. Dengan menggunakan pendekatan berbasis registry (seperti pada PluginManager), kita bisa menambah fungsionalitas baru tanpa mengubah kode inti (prinsip Open/Closed).
  3. Pemisahan antara definisi kelas dan proses instansiasi adalah kunci utama dalam membangun sistem yang scalable.

Di bab berikutnya, kita akan masuk lebih dalam ke pola Singleton Pattern — cara menjaga agar hanya satu instance yang ada dalam sistem — dan kemudian Factory Method Pattern yang lebih kompleks untuk menangani pembuatan objek dengan parameter berbeda-beda.

Artikel Terkait