Belajar OOP dengan Java - Design Patterns
Series/Belajar OOP/Episode 15
Episode 15 of 23

Belajar OOP dengan Java - Design Patterns

Menguasai pola desain Gang of Four yang paling sering dipakai: pola creational Singleton, Factory dan Builder, pola structural Adapter, Decorator dan Proxy, serta pola behavioral Observer, Strategy, Template Method dan Command — lengkap dengan contoh Java nyata.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Setelah SOLID memberi prinsip mengapa, episode ini memberi katalog bagaimana. Design patterns adalah solusi terbukti untuk masalah desain yang berulang — didokumentasikan dalam buku legendaris Design Patterns: Elements of Reusable Object-Oriented Software (1994) oleh Erich Gamma, Richard Helm, Ralph Johnson, dan John Vlissides — dikenal sebagai Gang of Four (GoF).

Mengapa penting? Karena pola bukan resep yang harus dihafal, melainkan kosakata. Saat seorang engineer berkata "pakai Strategy pattern", semua orang langsung paham bentuk solusinya. Episode ini membahas 10 pola paling sering dipakai dari 23 pola GoF, dibagi dalam tiga kelompok: creational, structural, dan behavioral.

Pola Creational

Pola ini mengatur cara object diciptakan — melepaskan pemakai dari logika pembuatan.

Singleton

Memastikan sebuah class hanya punya satu instance di seluruh aplikasi. Dipakai untuk konfigurasi global, connection pool, atau logger.

JavaSingleton dengan eager init
public final class Konfigurasi {
    private static final Konfigurasi INSTANCE = new Konfigurasi();
    private final String apiUrl;
 
    private Konfigurasi() {
        this.apiUrl = System.getenv("API_URL");
    }
 
    public static Konfigurasi getInstance() {
        return INSTANCE;
    }
 
    public String getApiUrl() {
        return apiUrl;
    }
}

Kunci: constructor private (tidak bisa new dari luar) dan instance diakses lewat getInstance().

Warning

Singleton adalah pola yang paling sering disalahgunakan — hampir semua dependency yang "hanya satu" sebenarnya bisa dibuat di composition root (episode 14) dan di-inject. Gunakan Singleton hanya untuk hal yang secara teknis harus satu (koneksi, registry). Untuk logika bisnis, DI biasa lebih baik dan lebih mudah ditest.

Factory Method

Memindahkan logika "membuat object yang tepat" ke method tersendiri — pemanggil tidak perlu tahu tipe konkret:

JavaFactory method
public interface Parser {
    String parse(String data);
}
 
public class ParserFactory {
    public static Parser buat(String format) {
        return switch (format) {
            case "json" -> new JsonParser();
            case "xml" -> new XmlParser();
            default -> throw new IllegalArgumentException("Format tidak dikenal: " + format);
        };
    }
}

Pemanggil tinggal Parser parser = ParserFactory.buat("json") — kebalikan dari new langsung, dan memenuhi OCP (episode 14) saat jenis parser bertambah.

Builder

Membangun object yang punya banyak parameter opsional secara bertahap dan terbaca — persis StringBuilder, atau fluent builders di framework:

JavaBuilder pattern
public class Notif {
    private final String judul;
    private final String isi;
    private final boolean urgent;
 
    private Notif(String judul, String isi, boolean urgent) {
        this.judul = judul;
        this.isi = isi;
        this.urgent = urgent;
    }
 
    public static class Builder {
        private String judul = "";
        private String isi = "";
        private boolean urgent = false;
 
        public Builder judul(String judul) { this.judul = judul; return this; }
        public Builder isi(String isi) { this.isi = isi; return this; }
        public Builder urgent(boolean urgent) { this.urgent = urgent; return this; }
 
        public Notif build() {
            return new Notif(judul, isi, urgent);
        }
    }
}

Pemakaian:

java
Notif notif = new Notif.Builder()
        .judul("Update tersedia")
        .isi("Versi baru telah rilis")
        .urgent(true)
        .build();

Setiap setter mengembalikan Builder (fluent), dan build() menghasilkan object immutable (episode 6). Cocok ketika object punya banyak parameter opsional — lebih jelas daripada constructor 7 argumen.

Pola Structural

Pola ini mengatur komposisi class — bagaimana object disusun membentuk struktur yang lebih besar.

Adapter

Menerjemahkan antarmuka lama agar cocok dengan yang diharapkan pemakai — seperti colokan adapter listrik:

JavaAdapter pattern
public interface PemutarBaru {
    void putar(String file);
}
 
public class PemutarLama {
    public void putarWav(String wav) { /* logika lama */ }
}
 
public class AdapterPemutar implements PemutarBaru {
    private final PemutarLama lama = new PemutarLama();
 
    @Override
    public void putar(String file) {
        lama.putarWav(file); // terjemahkan format baru ke API lama
    }
}

Pemakai PemutarBaru tidak berubah; implementasi lama dibungkus adapter.

Decorator

Menambah perilaku ke object tanpa mengubah class-nya — membungkus object dengan object lain yang setipe. Contoh klasik: stream Java:

JavaDecorator pattern
import java.io.*;
 
BufferedReader buffered = new BufferedReader(
        new InputStreamReader(
                new FileInputStream("data.txt")));

Setiap lapisan new ...(...) menambah perilaku (buffering, decoding karakter) tanpa mengubah class lain. Decorator adalah alternatif inheritance yang jauh lebih fleksibel — tambah perilaku dengan komposisi, bukan hierarki (ingat episode 7).

Proxy

Pemanggilan tidak langsung ke object asli, dengan kontrol tambahan — lazy loading, access control, caching:

JavaProxy pattern
public interface Layanan {
    String data();
}
 
public class LayananNyata implements Layanan {
    public String data() { return "data mahal dari server"; }
}
 
public class ProxyCache implements Layanan {
    private final Layanan asli = new LayananNyata();
    private String cache;
 
    public String data() {
        if (cache == null) {
            cache = asli.data();
        }
        return cache;
    }
}

Pemakai tidak tahu bahwa di belakangnya ada cache — pola ini transparan. Sekali dibungkus Proxy, kita punya Facade juga di dekatnya: Facade menyederhanakan banyak subsystem menjadi satu pintu masuk.

Facade

Menyediakan antarmuka sederhana di depan sistem yang rumit:

JavaFacade pattern
public class TampilanRumah {
    private final Lampu lampu = new Lampu();
    private final AC ac = new AC();
    private final TV tv = new TV();
 
    public void modeBioskop() {
        lampu.redup();
        ac.setSuhu(24);
        tv.nyalakan();
    }
}

Pemakai cukup memanggil modeBioskop() — tidak perlu tahu urutan operasi tiap subsystem. Hampir semua framework adalah Facade: Spring Boot menyembunyikan ratusan konfigurasi di balik API sederhana.

Pola Behavioral

Pola ini mengatur komunikasi antar object.

Strategy

Meng-enkapsulasi algoritma dan memilihnya saat runtime — wujud nyata polymorphism (episode 8):

JavaStrategy pattern
public interface Potongan {
    double hitung(double total);
}
 
public class PotonganBiasa implements Potongan {
    public double hitung(double total) { return total * 0.1; }
}
 
public class PotonganHariBesar implements Potongan {
    public double hitung(double total) { return total * 0.25; }
}
JavaMemilih strategi saat runtime
public class Kasir {
    private Potongan potongan;
 
    public void setPotongan(Potongan potongan) {
        this.potongan = potongan;
    }
 
    public double bayar(double total) {
        return total - potongan.hitung(total);
    }
}

setPotongan mengganti algoritma kapan saja tanpa mengubah Kasir. Ini contoh sempurna kombinasi OCP + DI + polymorphism — dan alasan kenapa kalian wajib memahami episode 8-14 sebelum episode ini.

Observer

Satu object memberi tahu banyak object lain saat ada perubahan — fondasi event system dan UI:

JavaObserver pattern
import java.util.ArrayList;
import java.util.List;
 
public class Stock {
    private final List<Runnable> pendengar = new ArrayList<>();
 
    public void tambahPendengar(Runnable r) {
        pendengar.add(r);
    }
 
    public void update() {
        for (Runnable r : pendengar) {
            r.run();
        }
    }
}

Setiap pendengar tidak tahu satu sama lain; update() cukup menyiarkan. UI Java, Spring events, dan pub/sub semuanya memakai ide ini.

Template Method

Menentukan kerangka algoritma di superclass, membiarkan subclass mengisi langkah spesifik:

JavaTemplate method
public abstract class ProsesData {
    public final void jalankan() {
        bacaData();
        proses();
        simpanHasil();
    }
 
    abstract void bacaData();
    abstract void proses();
    abstract void simpanHasil();
}

Subclass hanya mengisi tiga langkah; urutan (template) dijamin superclass. Ini memakai abstract class (episode 9) untuk berbagi alur tetap.

Command

Membungkus aksi menjadi object — memungkinkan undo, queue, dan logging aksi:

JavaCommand pattern
public interface Perintah {
    void eksekusi();
}
 
public class PerintahCetak implements Perintah {
    private final Dokumen dokumen;
 
    PerintahCetak(Dokumen dokumen) {
        this.dokumen = dokumen;
    }
 
    public void eksekusi() {
        dokumen.cetak();
    }
}

Command memisahkan "siapa yang memerintahkan" dari "siapa yang mengerjakan" — dipakai di queue task, undo/redo editor, dan transaction scripting.

Tip

Jangan menghafal 23 pola. Kenali masalahnya dulu, lalu lihat katalog. Pola yang paling sering dipakai di produksi Java sehari-hari: Builder, Factory, Strategy, Decorator, Proxy, dan Command. Jika sebuah pola membuat kode lebih rumit, kemungkinan kalian sedang salah memakai pola — bukan polanya yang salah.

Penutup

Inti yang harus dibawa pulang:

  • Creational (Singleton, Factory, Builder): mengatur cara object lahir — Builder untuk parameter banyak, Factory untuk logika pemilihan.
  • Structural (Adapter, Decorator, Proxy, Facade): menyusun object — komposisi alih-alih warisan.
  • Behavioral (Strategy, Observer, Template Method, Command): mengatur komunikasi dan algoritma.
  • Pola adalah kosakata desain — pahami masalahnya, bukan hafalan nama.
  • Hampir semua pola adalah aplikasi SOLID + polymorphism yang sudah kalian pelajari.

Di episode 16 selanjutnya kita akan membahas testing OOP dengan JUnit — @Test, @BeforeEach, assertions, test naming, lalu mocking dengan Mockito dan test doubles. Sampai jumpa di episode 16!

Belajar OOP dengan Java - Design Patterns | Belajar OOP