Pendahuluan — pola yang merapikan Capstone
Di Capstone Perpustakaan Mini (#49) kamu sudah punya class, inheritance, polymorphism, ABC, composition, dan special methods dalam satu sistem. Tier 2 mulai di sini: bukan konsep OOP baru, melainkan design pattern ringan — resep nama yang sering dipakai agar kode tidak mengeras di hutan if.
Model di artikel ini sengaja lebih ramping daripada Capstone (fokus label(), tanpa alur stok/pinjam) supaya Factory & Strategy tidak tenggelam di detail CLI.
Dua pola yang paling berguna untuk domain perpustakaan kita:
- Factory — satu pintu membuat object (
Buku/Ebook) tanpa pemanggil harus tahu class mana. - Strategy — aturan yang bisa diganti (misalnya perhitungan denda) tanpa menulis ulang layanan yang memanggilnya.
Keduanya berdiri di atas Polymorphism (#45), kontrak ABC (#46), dan “siapa punya siapa” dari Composition (#47). Jangan over-engineer: dua class + satu factory sudah cukup untuk belajar.
Prasyarat: Selesai Capstone (#49). Nyaman dengan Polymorphism (#45) · ABC (#46) · Composition (#47). Fondasi: Encapsulation (#43) · Inheritance (#44) · Special Methods (#48) · Mengenal OOP (#40).
Masalah: hutan if di mana-mana
Tanpa pola, pembuatan item dan aturan denda sering tersebar:
class Buku:
def __init__(self, judul, penulis):
self.judul = judul
self.penulis = penulis
class Ebook(Buku):
def __init__(self, judul, penulis, format_file="pdf"):
super().__init__(judul, penulis)
self.format_file = format_file
def buat_item_lama(jenis, judul, penulis, format_file=None):
if jenis == "buku":
return Buku(judul, penulis)
elif jenis == "ebook":
return Ebook(judul, penulis, format_file or "pdf")
else:
raise ValueError(f"jenis tidak dikenal: {jenis}")
def hitung_denda_lama(hari_terlambat, mode="flat"):
if mode == "flat":
return 5000 if hari_terlambat > 0 else 0
elif mode == "per_hari":
return max(0, hari_terlambat) * 1000
else:
raise ValueError("mode denda tidak dikenal")
print(type(buat_item_lama("buku", "ESP32", "Budi")).__name__)
print(hitung_denda_lama(3, "per_hari"))
Output:
Buku
3000
Fungsi di atas masih “jalan”, tapi setiap tempat yang butuh item baru atau mode denda baru akan menyalin cabang yang sama. Factory & Strategy memindahkan cabang itu ke satu tempat yang punya nama jelas.
Factory — satu pintu membuat item
Factory di level Tier 2 (skala Capstone) cukup berupa fungsi (atau method class) yang mengembalikan subtype yang tepat. Pemanggil hanya tahu “saya butuh item”, bukan detail class:
from dataclasses import dataclass
@dataclass
class Buku:
judul: str
penulis: str
def label(self) -> str:
return f"Buku: {self.judul} · {self.penulis}"
@dataclass
class Ebook(Buku):
format_file: str = "pdf"
def label(self) -> str:
return f"Ebook ({self.format_file}): {self.judul} · {self.penulis}"
def buat_item(jenis: str, judul: str, penulis: str, format_file: str = "pdf"):
jenis = jenis.lower().strip()
if jenis == "buku":
return Buku(judul, penulis)
if jenis == "ebook":
return Ebook(judul, penulis, format_file)
raise ValueError(f"jenis tidak dikenal: {jenis}")
items = [
buat_item("buku", "ESP32 Praktis", "Budi"),
buat_item("ebook", "Python OOP", "Sari", "epub"),
]
for item in items:
print(item.label())
try:
buat_item("majalah", "X", "Y")
except ValueError as e:
print("error:", e)
Output:
Buku: ESP32 Praktis · Budi
Ebook (epub): Python OOP · Sari
error: jenis tidak dikenal: majalah
Loop memakai label() yang sama — itu polymorphism dari Polymorphism (#45). Factory hanya menjawab: object mana yang dibuat. Cabang ValueError menjaga jenis yang belum didaftarkan gagal jelas di satu tempat.
Kenapa tidak membiarkan pemanggil menulis Buku(...) / Ebook(...) langsung? Boleh, untuk kode kecil. Factory berguna saat jenis datang dari string (form, JSON, CLI) atau saat nanti kamu menambah Audiobook tanpa menyentuh sepuluh pemanggil.
Strategy — ganti aturan tanpa tulis ulang kasir
Strategy = object yang mewakili satu cara melakukan sesuatu. Kasir (atau perpustakaan) punya strategi denda lewat composition, lalu memanggil antarmuka yang sama:
from abc import ABC, abstractmethod
class StrategiDenda(ABC):
@abstractmethod
def hitung(self, hari_terlambat: int) -> int:
"""Kembalikan denda dalam rupiah."""
class DendaFlat(StrategiDenda):
def __init__(self, nominal: int = 5000):
self.nominal = nominal
def hitung(self, hari_terlambat: int) -> int:
return self.nominal if hari_terlambat > 0 else 0
class DendaPerHari(StrategiDenda):
def __init__(self, per_hari: int = 1000):
self.per_hari = per_hari
def hitung(self, hari_terlambat: int) -> int:
return max(0, hari_terlambat) * self.per_hari
class Kasir:
def __init__(self, strategi: StrategiDenda):
self.strategi = strategi
def denda(self, hari_terlambat: int) -> int:
return self.strategi.hitung(hari_terlambat)
kasir_a = Kasir(DendaFlat(5000))
kasir_b = Kasir(DendaPerHari(1000))
print("flat 3 hari:", kasir_a.denda(3))
print("per hari 3 hari:", kasir_b.denda(3))
print("flat 0 hari:", kasir_a.denda(0))
Output:
flat 3 hari: 5000
per hari 3 hari: 3000
flat 0 hari: 0
Kasir tidak peduli flat atau per hari — ia hanya memanggil hitung. Itu Strategy + composition. Kontrak ABC memastikan subclass baru tidak “lupa” method (lihat ABC (#46)). Di runtime kamu bisa ganti: kasir_a.strategi = DendaPerHari(1500).
Gabungkan di domain Capstone
Factory membuat koleksi; Strategy mengatur denda. Keduanya hidup berdampingan tanpa saling mengunci. Di Capstone, titik masuk praktis: ganti pembuatan Buku/Ebook mentah dengan buat_item, dan pindahkan rumus denda ke object strategi yang dipegang layanan (bukan diwariskan ke class buku).
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Buku:
judul: str
penulis: str
def label(self) -> str:
return f"Buku: {self.judul} · {self.penulis}"
@dataclass
class Ebook(Buku):
format_file: str = "pdf"
def label(self) -> str:
return f"Ebook ({self.format_file}): {self.judul} · {self.penulis}"
def buat_item(jenis, judul, penulis, format_file="pdf"):
key = jenis.lower().strip()
if key == "buku":
return Buku(judul, penulis)
if key == "ebook":
return Ebook(judul, penulis, format_file)
raise ValueError(f"jenis tidak dikenal: {jenis}")
class StrategiDenda(ABC):
@abstractmethod
def hitung(self, hari_terlambat: int) -> int:
raise NotImplementedError
class DendaPerHari(StrategiDenda):
def __init__(self, per_hari=1000):
self.per_hari = per_hari
def hitung(self, hari_terlambat: int) -> int:
return max(0, hari_terlambat) * self.per_hari
class Kasir:
def __init__(self, strategi: StrategiDenda):
self.strategi = strategi
def denda(self, hari_terlambat: int) -> int:
return self.strategi.hitung(hari_terlambat)
koleksi = [
buat_item("buku", "MQTT Dasar", "Andi"),
buat_item("ebook", "Flask Ringkas", "Dewi", "pdf"),
]
kasir = Kasir(DendaPerHari(1000))
for item in koleksi:
print(item.label(), "-> denda 2 hari:", kasir.denda(2))
Output:
Buku: MQTT Dasar · Andi -> denda 2 hari: 2000
Ebook (pdf): Flask Ringkas · Dewi -> denda 2 hari: 2000
Pola Dasar — Factory & Strategy
-
1
Kenali cabang yang tersebar String jenis item atau mode denda yang di-
ifdi banyak file -> kandidat Factory/Strategy. -
2
Factory untuk “apa yang dibuat”
buat_item(jenis, ...)mengembalikanBuku/Ebook; pemanggil tidak import semua class. -
3
Strategy untuk “bagaimana dihitung” ABC
hitung+DendaFlat/DendaPerHari; layanan hanya menyimpan referensi strategi. -
4
Tempel lewat composition
Kasir/Perpustakaanpunya strategi — jangan warisi denda ke class buku (lihat Composition (#47)). -
5
Berhenti sebelum over-engineer Dua–tiga strategi + satu factory cukup. Jangan tambah AbstractFactory/Singleton “karena keren”.
Kode lengkap — factory_strategy_perpustakaan.py
Simpan sebagai factory_strategy_perpustakaan.py, lalu jalankan python factory_strategy_perpustakaan.py. Skenario memakai demo() tetap — tidak ada prompt interaktif yang menggantung.
"""Factory & Strategy ringan — domain perpustakaan (Tier 2 #50)."""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Buku:
judul: str
penulis: str
def label(self) -> str:
return f"Buku: {self.judul} · {self.penulis}"
@dataclass
class Ebook(Buku):
format_file: str = "pdf"
def label(self) -> str:
return f"Ebook ({self.format_file}): {self.judul} · {self.penulis}"
def buat_item(
jenis: str,
judul: str,
penulis: str,
format_file: str = "pdf",
) -> Buku:
key = jenis.lower().strip()
if key == "buku":
return Buku(judul, penulis)
if key == "ebook":
return Ebook(judul, penulis, format_file)
raise ValueError(f"jenis tidak dikenal: {jenis}")
class StrategiDenda(ABC):
@abstractmethod
def hitung(self, hari_terlambat: int) -> int:
raise NotImplementedError
class DendaFlat(StrategiDenda):
def __init__(self, nominal: int = 5000) -> None:
self.nominal = nominal
def hitung(self, hari_terlambat: int) -> int:
return self.nominal if hari_terlambat > 0 else 0
class DendaPerHari(StrategiDenda):
def __init__(self, per_hari: int = 1000) -> None:
self.per_hari = per_hari
def hitung(self, hari_terlambat: int) -> int:
return max(0, hari_terlambat) * self.per_hari
class Perpustakaan:
"""Composition: punya koleksi + strategi denda."""
def __init__(self, nama: str, strategi: StrategiDenda) -> None:
self.nama = nama
self.strategi = strategi
self._items: list[Buku] = []
def tambah(self, jenis: str, judul: str, penulis: str, format_file: str = "pdf") -> Buku:
item = buat_item(jenis, judul, penulis, format_file)
self._items.append(item)
return item
def denda(self, hari_terlambat: int) -> int:
return self.strategi.hitung(hari_terlambat)
def ganti_strategi(self, strategi: StrategiDenda) -> None:
self.strategi = strategi
@property
def items(self) -> list[Buku]:
"""Salinan baca — jangan mutasi stok dari luar (encapsulation)."""
return list(self._items)
def __str__(self) -> str:
return f"{self.nama}: {len(self._items)} item"
def demo() -> None:
lib = Perpustakaan("Kota A", DendaFlat(5000))
lib.tambah("buku", "ESP32 Praktis", "Budi")
lib.tambah("ebook", "Python OOP", "Sari", "epub")
print(lib)
for item in lib.items:
print(" -", item.label())
print("denda flat 4 hari:", lib.denda(4))
lib.ganti_strategi(DendaPerHari(1000))
print("denda per hari 4 hari:", lib.denda(4))
print("denda 0 hari:", lib.denda(0))
if __name__ == "__main__":
demo()
Output yang diharapkan:
Kota A: 2 item
- Buku: ESP32 Praktis · Budi
- Ebook (epub): Python OOP · Sari
denda flat 4 hari: 5000
denda per hari 4 hari: 4000
denda 0 hari: 0
Perpustakaan.tambah memakai Factory; denda / ganti_strategi memakai Strategy. __str__ mengikuti kebiasaan dari Special Methods (#48). Property items mengikuti semangat Encapsulation (#43) — koleksi tidak diutak-atik lewat _items dari luar.
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
Pemanggil masih penuh if jenis |
Factory ada tapi tidak dipakai | Semua pembuatan lewat buat_item / method factory |
| Denda di-hardcode di method pinjam | Belum dipisah sebagai strategi | Pindahkan rumus ke class strategi (method hitung); layanan hanya memanggil |
Buku mewarisi DendaFlat |
Salah pakai inheritance | Composition: layanan punya strategi, buku tetap data |
| AbstractFactory + 12 class untuk 2 jenis | Over-engineer | Fungsi factory + 2–3 strategy cukup untuk skala Capstone |
Mutasi lib._items dari luar |
Melewati encapsulation koleksi | Pakai property/method publik (items / tambah) — lihat Encapsulation (#43) |
ValueError: jenis tidak dikenal |
Typo string / belum daftarkan jenis baru | Normalisasi lower().strip(); tambah cabang di satu tempat |
Latihan singkat
- Tambah jenis
audiobookdibuat_item(lihat pola inheritance di Inheritance (#44)) dengan fielddurasi_menit. - Buat
DendaBertingkat: 0 hari = 0; 1–3 hari = 2000; >3 hari = 2000 + 1500 per hari ekstra. - Ubah
demo()agar mencetak denda untuk setiap item (tetap tanpa prompt interaktif).
FAQ singkat
Apa bedanya Factory dengan constructor biasa?
Constructor membuat satu class. Factory memilih class mana berdasarkan input (sering string/enum) lalu memanggil constructor yang tepat.
Haruskah Strategy selalu pakai ABC?
Tidak wajib. Duck typing + method hitung sudah polymorphism (Polymorphism (#45)). ABC membantu IDE/kontrak saat tim membesar — pola yang sama di ABC (#46).
Apakah ini menggantikan Capstone?
Tidak. Capstone (#49) adalah sistem utuh. Artikel ini menambahkan dua resep agar Capstone lebih mudah dikembangkan tanpa hutan if.
Kapan tidak perlu pattern?
Satu jenis object dan satu rumus denda selamanya -> class biasa cukup. Pattern dibayar dengan indirection; pakai saat cabang mulai menyebar.
Lanjut ke mana?
Ide Tier 2 berikutnya: OOP di MicroPython / ESP32 (jembatan ke Seri IoT) — belum live, jadi belum ada slug hardlink. Setelah itu: class di Flask/FastAPI sebagai pintu Web Dev.
Kesimpulan & langkah berikutnya
Factory memusatkan pembuatan object; Strategy memusatkan perilaku yang bisa diganti. Keduanya memakai polymorphism + composition — bukan hierarchy baru yang rumit.
Artikel ini adalah #50 (ini) — pembuka Tier 2 setelah Seri 3 selesai di Capstone (#49).
Berikutnya (backlog): OOP di MicroPython untuk node IoT, lalu class di Flask/FastAPI. Seri 3 tetap fondasi yang sudah lengkap (sepuluh artikel inti sampai Capstone).
Tier 2 progress: langkah #50 (ini) · Seri 3 tetap 10/10 live. Prasyarat: Capstone (#49) · Polymorphism (#45) · ABC (#46) · Composition (#47) · Special Methods (#48) · Encapsulation (#43) · Inheritance (#44) · Mengenal OOP (#40).