Pendahuluan — capstone Seri 3
Selamat! Kamu sudah menempuh sembilan artikel Seri 3 — dari Mengenal OOP (#40) sampai Special Methods & Dataclass (#48). Artikel ini adalah capstone penutup: satu proyek kecil yang menggabungkan class, encapsulation, inheritance, polymorphism, ABC, composition, dan dataclass dalam domain yang familiar — sistem perpustakaan mini.
Bukan soal membuat aplikasi produksi. Tujuannya: membuktikan bahwa potongan konsep Seri 3 bisa saling mengunci dalam satu file yang bisa kamu jalankan.
Prasyarat: Sudah baca Special Methods & Dataclass (#48). Fondasi: Composition (#47) · ABC (#46) · Polymorphism (#45) · Inheritance (#44) · Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).
Spesifikasi fitur
Perpustakaan mini kita mendukung lima operasi inti:
- Tambah — masukkan buku/ebook ke koleksi
- Pinjam — kurangi stok jika tersedia
- Kembalikan — kembalikan stok setelah dipinjam
- Cari — temukan item berdasarkan judul (substring)
- Laporan — cetak ringkasan koleksi + status stok
Semua dijalankan lewat fungsi demo() yang mensimulasikan perintah CLI — tanpa menunggu keyboard, supaya skrip bisa diuji otomatis.
Arsitektur folder mini
Di proyek nyata kamu bisa memisah file. Untuk capstone ini kita tetap satu file supaya mudah disalin — tapi pikirkan batas tanggung jawab seperti folder berikut:
models/, aturan bisnis di services/, pintu masuk di main.py.Model data — dataclass, inheritance, ABC
Kita pakai @dataclass untuk Buku (lihat #48), Ebook(Buku) untuk inheritance (#44), dan ABC Pinjaman dengan kontrak pinjam/kembalikan (#46):
from abc import ABC, abstractmethod
from dataclasses import dataclass
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool:
...
@abstractmethod
def kembalikan(self) -> bool:
...
@dataclass
class Buku(Pinjaman):
judul: str
penulis: str
stok: int = 1
def pinjam(self) -> bool:
if self.stok < 1:
return False
self.stok -= 1
return True
def kembalikan(self) -> bool:
self.stok += 1
return True
def info(self) -> str:
return f"Buku: {self.judul} · {self.penulis} (stok={self.stok})"
@dataclass
class Ebook(Buku):
format_file: str = "PDF"
def info(self) -> str:
return f"Ebook: {self.judul} · {self.format_file} (stok={self.stok})"
b = Buku("ESP32 Praktis", "Budi", stok=2)
e = Ebook("Belajar Python", "Sari", stok=1, format_file="EPUB")
print(b.info())
print(e.info())
print("pinjam buku:", b.pinjam(), "stok=", b.stok)
Output:
Buku: ESP32 Praktis · Budi (stok=2)
Ebook: Belajar Python · EPUB (stok=1)
pinjam buku: True stok= 1
Ebook mewarisi pinjam/kembalikan dari Buku, lalu menimpa info() — inheritance + polymorphism dalam satu langkah.
Batas encapsulation (#43) di sini sederhana: stok hanya berubah lewat method pinjam/kembalikan, bukan diubah bebas dari luar. (Naik ke @property boleh belakangan — lihat FAQ.)
Jembatan ke Special Methods (#48): @dataclass sudah menyediakan __init__, __repr__, dan __eq__. Untuk teks domain kita tetap pakai method info(); di kode lengkap nanti kita tambahkan __str__ yang memanggil info() supaya print(buku) ikut ramah manusia.
ABC menolak instance langsung — sama seperti di ABC (#46):
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool:
...
@abstractmethod
def kembalikan(self) -> bool:
...
try:
Pinjaman()
except TypeError as err:
print("ABC menolak:", type(err).__name__)
Output:
ABC menolak: TypeError
Composition — class Perpustakaan punya koleksi
Sesuai Composition (#47): Perpustakaan punya daftar item, bukan “adalah” daftar:
from dataclasses import dataclass
from abc import ABC, abstractmethod
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool: ...
@abstractmethod
def kembalikan(self) -> bool: ...
@dataclass
class Buku(Pinjaman):
judul: str
penulis: str
stok: int = 1
def pinjam(self) -> bool:
if self.stok < 1:
return False
self.stok -= 1
return True
def kembalikan(self) -> bool:
self.stok += 1
return True
def info(self) -> str:
return f"Buku: {self.judul} (stok={self.stok})"
class Perpustakaan:
def __init__(self, nama: str):
self.nama = nama
self.koleksi = [] # composition: punya items
def tambah(self, item: Buku) -> None:
self.koleksi.append(item)
def cari(self, kata: str):
k = kata.lower()
return [i for i in self.koleksi if k in i.judul.lower()]
def laporan(self) -> str:
baris = [f"=== {self.nama} ({len(self.koleksi)} item) ==="]
for i in self.koleksi:
baris.append(i.info())
return "\n".join(baris)
p = Perpustakaan("Kota A")
p.tambah(Buku("ESP32 Praktis", "Budi", stok=2))
p.tambah(Buku("MQTT Dasar", "Ani", stok=1))
print(p.laporan())
print("cari 'mqtt':", [x.judul for x in p.cari("mqtt")])
Output:
=== Kota A (2 item) ===
Buku: ESP32 Praktis (stok=2)
Buku: MQTT Dasar (stok=1)
cari 'mqtt': ['MQTT Dasar']
Polymorphism — loop info() pada campuran Buku/Ebook
Satu loop, dua tipe — pola dari Polymorphism (#45):
from abc import ABC, abstractmethod
from dataclasses import dataclass
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool: ...
@abstractmethod
def kembalikan(self) -> bool: ...
@dataclass
class Buku(Pinjaman):
judul: str
penulis: str
stok: int = 1
def pinjam(self) -> bool:
if self.stok < 1:
return False
self.stok -= 1
return True
def kembalikan(self) -> bool:
self.stok += 1
return True
def info(self) -> str:
return f"Buku: {self.judul}"
@dataclass
class Ebook(Buku):
format_file: str = "PDF"
def info(self) -> str:
return f"Ebook: {self.judul} [{self.format_file}]"
items = [
Buku("ESP32 Praktis", "Budi"),
Ebook("Belajar Python", "Sari", format_file="EPUB"),
Buku("MQTT Dasar", "Ani"),
]
for item in items:
print(item.info()) # method yang tepat dipanggil otomatis
Output:
Buku: ESP32 Praktis
Ebook: Belajar Python [EPUB]
Buku: MQTT Dasar
Pinjam sampai stok habis — method mengembalikan False, bukan error:
from abc import ABC, abstractmethod
from dataclasses import dataclass
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool: ...
@abstractmethod
def kembalikan(self) -> bool: ...
@dataclass
class Buku(Pinjaman):
judul: str
penulis: str
stok: int = 1
def pinjam(self) -> bool:
if self.stok < 1:
return False
self.stok -= 1
return True
def kembalikan(self) -> bool:
self.stok += 1
return True
b = Buku("MQTT Dasar", "Ani", stok=1)
print("1:", b.pinjam(), "stok=", b.stok)
print("2:", b.pinjam(), "stok=", b.stok) # False — stok habis
print("kembali:", b.kembalikan(), "stok=", b.stok)
Output:
1: True stok= 0
2: False stok= 0
kembali: True stok= 1
CLI demo — menu lewat skrip, bukan interaktif
Di CLI interaktif biasanya ada menu: 1) tambah 2) pinjam 3) kembalikan 4) cari 5) laporan. Untuk artikel & CI, kita tidak memakai input(). Fungsi demo() memanggil method yang sama secara berurutan — hasilnya deterministic dan bisa di-py_compile.
from abc import ABC, abstractmethod
from dataclasses import dataclass
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool: ...
@abstractmethod
def kembalikan(self) -> bool: ...
@dataclass
class Buku(Pinjaman):
judul: str
penulis: str
stok: int = 1
def pinjam(self) -> bool:
if self.stok < 1:
return False
self.stok -= 1
return True
def kembalikan(self) -> bool:
self.stok += 1
return True
def info(self) -> str:
return f"{self.judul} (stok={self.stok})"
@dataclass
class Ebook(Buku):
format_file: str = "PDF"
def info(self) -> str:
return f"{self.judul} [{self.format_file}] (stok={self.stok})"
class Perpustakaan:
def __init__(self, nama):
self.nama = nama
self.koleksi = []
def tambah(self, item):
self.koleksi.append(item)
def cari(self, kata):
k = kata.lower()
return [i for i in self.koleksi if k in i.judul.lower()]
def laporan(self):
return "\n".join(i.info() for i in self.koleksi)
def demo():
p = Perpustakaan("Mini")
p.tambah(Buku("ESP32 Praktis", "Budi", stok=2))
p.tambah(Ebook("Belajar Python", "Sari", format_file="EPUB"))
# perintah terscript — setara menu: pinjam, kembalikan, cari, laporan
print("pinjam:", p.koleksi[0].pinjam())
print("kembalikan:", p.koleksi[0].kembalikan())
print("cari:", [x.judul for x in p.cari("python")])
print(p.laporan())
demo()
Output:
pinjam: True
kembalikan: True
cari: ['Belajar Python']
ESP32 Praktis (stok=2)
Belajar Python [EPUB] (stok=1)
Perhatikan: demo memanggil p.koleksi[0].pinjam() langsung — sengaja “bocor” ke item agar pola polymorphism terlihat. Latihan 1 meminta kamu naikkan ke Perpustakaan.pinjam_judul(...) supaya layanan yang mengorkestrasi.
Kalau nanti kamu ingin CLI interaktif di mesin sendiri, bungkus menu dengan loop + pembacaan keyboard — tapi jangan masukkan itu ke skrip yang harus jalan di CI/artikel.
Pola Dasar — lima langkah capstone
-
1
Tulis kontrak ABC dulu
Pinjamanmendefinisikanpinjam/kembalikansebelum detail stok. -
2
Model data dengan dataclass
Buku+Ebook(Buku)— field jelas, inheritance ringan. -
3
Composition di layanan
Perpustakaanpunyaself.koleksi— tambah, cari, laporan. -
4
Polymorphism lewat
info()Loop campuran Buku/Ebook tanpaif isinstanceberlebihan. -
5
Demo terscript, deterministic Satu fungsi
demo()memanggil alur pinjam/kembalikan/cari/laporan agar bisa diuji.
__str__ di kode lengkap).Kode lengkap — salin & jalankan
Simpan sebagai perpustakaan_mini.py, lalu python perpustakaan_mini.py:
from abc import ABC, abstractmethod
from dataclasses import dataclass
class Pinjaman(ABC):
@abstractmethod
def pinjam(self) -> bool: ...
@abstractmethod
def kembalikan(self) -> bool: ...
@dataclass
class Buku(Pinjaman):
judul: str
penulis: str
stok: int = 1
def pinjam(self) -> bool:
if self.stok < 1:
return False
self.stok -= 1
return True
def kembalikan(self) -> bool:
self.stok += 1
return True
def info(self) -> str:
return f"Buku: {self.judul} · {self.penulis} (stok={self.stok})"
def __str__(self) -> str:
return self.info() # special method (#48): print(buku) pakai ini
@dataclass
class Ebook(Buku):
format_file: str = "PDF"
def info(self) -> str:
return f"Ebook: {self.judul} · {self.format_file} (stok={self.stok})"
class Perpustakaan:
def __init__(self, nama: str):
self.nama = nama
self.koleksi = []
def tambah(self, item: Buku) -> None:
self.koleksi.append(item)
def cari(self, kata: str):
k = kata.lower()
return [i for i in self.koleksi if k in i.judul.lower()]
def laporan(self) -> str:
baris = [f"=== {self.nama} ({len(self.koleksi)} item) ==="]
for i in self.koleksi:
baris.append(" - " + str(i)) # __str__ -> info()
return "\n".join(baris)
def demo():
p = Perpustakaan("Kota A")
p.tambah(Buku("ESP32 Praktis", "Budi", stok=2))
p.tambah(Ebook("Belajar Python", "Sari", format_file="EPUB"))
p.tambah(Buku("MQTT Dasar", "Ani", stok=1))
print("pinjam ESP32:", p.koleksi[0].pinjam())
print("pinjam lagi:", p.koleksi[0].pinjam())
print("kembalikan:", p.koleksi[0].kembalikan())
print("cari 'python':", [x.judul for x in p.cari("python")])
print("print satu item:", p.koleksi[1]) # __str__ / polymorphism
print(p.laporan())
if __name__ == "__main__":
demo()
Output yang diharapkan (kurang lebih):
pinjam ESP32: True
pinjam lagi: True
kembalikan: True
cari 'python': ['Belajar Python']
print satu item: Ebook: Belajar Python · EPUB (stok=1)
=== Kota A (3 item) ===
- Buku: ESP32 Praktis · Budi (stok=1)
- Ebook: Belajar Python · EPUB (stok=1)
- Buku: MQTT Dasar · Ani (stok=1)
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
TypeError: Can't instantiate abstract class |
Subclass belum implementasi semua @abstractmethod |
Lengkapkan pinjam dan kembalikan (lihat ABC #46) |
| Stok jadi negatif | pinjam tidak cek stok < 1 |
Return False lebih awal; jangan kurangi stok kosong |
Perpustakaan diwariskan dari list |
Mencampur “adalah list” dengan “punya koleksi” | Composition: self.koleksi = [] (#47) |
| Demo macet menunggu keyboard | Skrip menunggu ketikan pengguna | Ganti dengan demo() terscript |
info() selalu teks Buku untuk Ebook |
Lupa override di subclass | Override info() di Ebook — polymorphism |
| Field dataclass hilang di subclass | Urutan field default di inheritance dataclass | Field tanpa default dulu; field ber-default di subclass (Seri 3: mulai sederhana) |
Latihan singkat
- Tambah method
Perpustakaan.pinjam_judul(judul)yang mencari item lalu memanggilpinjam(). - Tolak duplikat judul saat
tambah(bandingkan case-insensitive). - Buat subclass
Audiobook(Buku)(pola di Inheritance (#44)) dengan fielddurasi_menitdan overrideinfo().
FAQ singkat
Kenapa satu file, bukan folder models/services?
Supaya mudah disalin & diuji. Pola folder tetap berlaku saat proyek membesar.
Apa bedanya dengan encapsulation di #43?
Di sini stok diubah lewat method pinjam/kembalikan. Kamu bisa naikkan ke @property bila ingin validasi lebih ketat.
Apakah ABC wajib untuk proyek kecil?
Tidak wajib, tapi membantu menegaskan kontrak sebelum implementasi — terutama bila ada banyak tipe item.
Bolehkah CLI interaktif di rumah?
Boleh untuk latihan. Untuk artikel/CI, tetap pakai demo() terscript.
Ke mana setelah Seri 3?
Lihat kesimpulan di bawah — ide Tier 2 tanpa menunggu slug yang belum live.
Indeks Seri 3 lengkap
- Mengenal OOP — Cara Berpikir dengan Objek (#40)
- Class dan Object Pertama di Python (#41)
- Attribute, Method & Constructor
__init__(#42) - Encapsulation & Property (#43)
- Inheritance — Pewarisan Class (#44)
- Polymorphism di Python OOP (#45)
- Abstraction & ABC (#46)
- Composition vs Inheritance (#47)
- Special Methods & Dataclass (#48)
- Capstone: Sistem Perpustakaan Mini (#49 (ini)) — artikel ini (capstone)
Kesimpulan — Seri 3 selesai
Capstone ini merangkai seluruh Seri 3: berpikir objek (#40), class & object (#41), attribute/__init__ (#42), encapsulation (#43), inheritance (#44), polymorphism (#45), ABC (#46), composition (#47), special methods/dataclass (#48) — lalu satu CLI mini yang runnable.
Seri 3 resmi selesai (10/10 artikel live). Ide lanjut (Tier 2, belum jadi artikel live): API Flask + model OOP, persistensi JSON untuk koleksi, atau pola OOP ringan di MicroPython untuk node IoT. Kalau kamu dari jalur perangkat, pola “service punya sensor/aktuator” sudah muncul di capstone smart greenhouse (#39) — OOP membantu merapikan firmware yang membesar.
Artikel ini adalah #49 (ini) — penutup Seri 3 setelah Special Methods (#48).
Seri 3 progress: 10/10 artikel live. Kamu di langkah #49 (ini). Prasyarat: Special Methods (#48) · Composition (#47) · ABC (#46) · Polymorphism (#45) · Inheritance (#44) · Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).