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.

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 ini mengatur cara object diciptakan — melepaskan pemakai dari logika pembuatan.
Memastikan sebuah class hanya punya satu instance di seluruh aplikasi. Dipakai untuk konfigurasi global, connection pool, atau logger.
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.
Memindahkan logika "membuat object yang tepat" ke method tersendiri — pemanggil tidak perlu tahu tipe konkret:
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.
Membangun object yang punya banyak parameter opsional secara bertahap dan terbaca — persis StringBuilder, atau fluent builders di framework:
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:
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 ini mengatur komposisi class — bagaimana object disusun membentuk struktur yang lebih besar.
Menerjemahkan antarmuka lama agar cocok dengan yang diharapkan pemakai — seperti colokan adapter listrik:
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.
Menambah perilaku ke object tanpa mengubah class-nya — membungkus object dengan object lain yang setipe. Contoh klasik: stream Java:
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).
Pemanggilan tidak langsung ke object asli, dengan kontrol tambahan — lazy loading, access control, caching:
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.
Menyediakan antarmuka sederhana di depan sistem yang rumit:
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 ini mengatur komunikasi antar object.
Meng-enkapsulasi algoritma dan memilihnya saat runtime — wujud nyata polymorphism (episode 8):
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; }
}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.
Satu object memberi tahu banyak object lain saat ada perubahan — fondasi event system dan UI:
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.
Menentukan kerangka algoritma di superclass, membiarkan subclass mengisi langkah spesifik:
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.
Membungkus aksi menjadi object — memungkinkan undo, queue, dan logging aksi:
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.
Inti yang harus dibawa pulang:
Builder untuk parameter banyak, Factory untuk logika pemilihan.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!