Pendahuluan — “adalah” vs “punya”
Di Inheritance (#44) kita pakai is-a: ebook adalah buku. Di Abstraction & ABC (#46) kita tulis kontrak kemampuan. Hari ini pertanyaan berikutnya: kapan objek sebaiknya punya objek lain (composition, has-a), bukan mewarisi class lain?
Aturan praktis: warisi karena hubungan “adalah-sejenis” yang jujur. Kalau hanya butuh “menyimpan/mengelola” object lain — biasanya composition lebih bersih.
Prasyarat: Selesai Inheritance (#44) dan nyaman dengan ABC (#46). Fondasi: Polymorphism (#45) · Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).
Inheritance yang masih tepat — is-a
Cuplikan ringkas dari jalur Seri 3: ebook memang sejenis buku digital.
class Buku:
def __init__(self, judul, penulis):
self.judul = judul
self.penulis = penulis
def info(self):
return f"{self.judul} · {self.penulis}"
class Ebook(Buku):
def __init__(self, judul, penulis, format_file="pdf"):
super().__init__(judul, penulis)
self.format_file = format_file
def info(self):
return f"{super().info()} · {self.format_file.upper()}"
e = Ebook("Belajar Python", "Sari", "epub")
print(e.info())
print("Ebook adalah Buku?", isinstance(e, Buku))
Output: Belajar Python · Sari · EPUB lalu Ebook adalah Buku? True. Inheritance di sini jujur — bukan tipuan demi hemat mengetik.
Anti-pola — warisi hanya demi “punya daftar”
Kesalahan umum: membuat perpustakaan mewarisi Buku supaya “langsung dapat” atribut judul. Padahal perpustakaan bukan sebuah buku.
class Buku:
def __init__(self, judul):
self.judul = judul
# SALAH — Perpustakaan "adalah" Buku? Tidak masuk akal
class PerpustakaanSalah(Buku):
def __init__(self, nama, judul_buku_pertama):
super().__init__(judul_buku_pertama) # memaksa "judul" ke perpustakaan
self.nama = nama
p = PerpustakaanSalah("Kota A", "ESP32 Praktis")
print("nama:", p.nama)
print("judul (milik 'Buku'?):", p.judul)
print("isinstance perpustakaan->Buku:", isinstance(p, Buku)) # True — menyesatkan
Output kurang lebih:
nama: Kota A
judul (milik 'Buku'?): ESP32 Praktis
isinstance perpustakaan->Buku: True
isinstance bilang True, tapi model domainnya bohong. Itu sinyal inheritance dipakai sebagai “kantong atribut”, bukan hubungan is-a.
Composition — Perpustakaan punya koleksi Buku
Composition: object menyimpan object lain sebagai bagian (biasanya di list/dict/atribut). Perpustakaan punya banyak buku — bukan adalah buku.
class Buku:
def __init__(self, judul, penulis):
self.judul = judul
self.penulis = penulis
def info(self):
return f"{self.judul} · {self.penulis}"
class Perpustakaan:
def __init__(self, nama):
self.nama = nama
self.koleksi = [] # punya daftar Buku
def tambah(self, buku):
self.koleksi.append(buku)
def daftar(self):
return [b.info() for b in self.koleksi]
lib = Perpustakaan("Kota A")
lib.tambah(Buku("ESP32 Praktis", "Budi"))
lib.tambah(Buku("Belajar Python", "Sari"))
print(lib.nama)
for baris in lib.daftar():
print("-", baris)
print("isinstance lib->Buku:", isinstance(lib, Buku))
Output kurang lebih:
Kota A
- ESP32 Praktis · Budi
- Belajar Python · Sari
isinstance lib->Buku: False
Sekarang modelnya jujur: perpustakaan mengelola koleksi; tiap buku tetap object sendiri. Composition juga sering berpasangan dengan ABC (#46): class pemilik koleksi tidak perlu mewarisi kontrak — ia cukup menyimpan object yang sudah memenuhi antarmuka (mis. item yang bisa info() atau pinjam()).
Refactor — dari SALAH ke BENAR
Bandingkan sisi pemanggil. Composition membuat API perpustakaan berbicara tentang koleksi, bukan menyamar sebagai buku:
class Buku:
def __init__(self, judul):
self.judul = judul
class PerpustakaanSalah(Buku):
def __init__(self, nama, judul):
super().__init__(judul)
self.nama = nama
class PerpustakaanBenar:
def __init__(self, nama):
self.nama = nama
self.koleksi = []
def tambah(self, buku):
self.koleksi.append(buku)
def jumlah(self):
return len(self.koleksi)
salah = PerpustakaanSalah("Kota A", "ESP32 Praktis")
benar = PerpustakaanBenar("Kota A")
benar.tambah(Buku("ESP32 Praktis"))
benar.tambah(Buku("Cerita Sensor"))
print("SALAH judul di perpustakaan:", salah.judul)
print("BENAR jumlah buku:", benar.jumlah())
Output: SALAH judul di perpustakaan: ESP32 Praktis lalu BENAR jumlah buku: 2. Versi benar bisa tumbuh (cari, pinjam, statistik) tanpa memaksa perpustakaan “jadi” buku.
Kapan inheritance masih pilihan pertama?
- Is-a jujur —
Ebook(Buku),Audiobook(Buku) - Perilaku bersama + override — method induk dipakai ulang dengan variasi (lihat Polymorphism (#45))
- Kontrak formal — ABC di Abstraction (#46) memang memakai pewarisan untuk menegakkan antarmuka
Kalau alasanmu hanya “biar tidak copy-paste atribut” atau “biar dapat method list gratis” — berhenti sejenak. Composition (atau fungsi helper) sering lebih aman. Contoh klasik: mewarisi list:
class Buku:
def __init__(self, judul):
self.judul = judul
# SALAH — katalog "adalah" list? API jadi bocor ke method list mentah
class KatalogSalah(list):
def judul_semua(self):
return [b.judul for b in self]
# BENAR — katalog punya items
class KatalogBenar:
def __init__(self):
self.items = []
def tambah(self, buku):
self.items.append(buku)
def judul_semua(self):
return [b.judul for b in self.items]
salah = KatalogSalah([Buku("ESP32 Praktis"), Buku("Belajar Python")])
benar = KatalogBenar()
benar.tambah(Buku("ESP32 Praktis"))
benar.tambah(Buku("Belajar Python"))
print("SALAH isinstance list:", isinstance(salah, list))
print("BENAR isinstance list:", isinstance(benar, list))
print("BENAR judul:", benar.judul_semua())
Output kurang lebih:
SALAH isinstance list: True
BENAR isinstance list: False
BENAR judul: ['ESP32 Praktis', 'Belajar Python']
KatalogSalah “kebetulan” bisa append/extend seperti list — tapi tipe domainnya menyesatkan. Prefer bungkus list di dalam atribut.
Composition juga memudahkan menambah perilaku di pemilik koleksi — tanpa mengubah tipe Buku:
class Buku:
def __init__(self, judul, tahun):
self.judul = judul
self.tahun = tahun
class Perpustakaan:
def __init__(self, nama):
self.nama = nama
self.koleksi = []
def tambah(self, buku):
self.koleksi.append(buku)
def cari(self, kata):
kata = kata.lower()
return [b for b in self.koleksi if kata in b.judul.lower()]
lib = Perpustakaan("Kota A")
lib.tambah(Buku("ESP32 Praktis", 2023))
lib.tambah(Buku("Belajar Python", 2024))
hasil = lib.cari("python")
print([b.judul for b in hasil])
Output: ['Belajar Python']. Fitur pencarian hidup di perpustakaan — pemilik koleksi — bukan disisipkan lewat warisan aneh.
Pola Dasar — memilih composition atau inheritance
-
1
Uji kalimat is-a “X adalah Y?” Kalau terdengar aneh di dunia nyata, curigai inheritance.
-
2
Uji kalimat has-a “X punya Y / mengelola banyak Y?” → composition (atribut/koleksi).
-
3
Jangan warisi demi reuse buta Butuh perilaku mirip ≠ wajib jadi subclass. Ekstrak fungsi/helper atau composition.
-
4
Biarkan API pemilik koleksi
tambah,cari,jumlahhidup di class yang punya daftar. -
5
Campur dengan sadar Inheritance untuk is-a/kontrak; composition untuk struktur “punya”. Keduanya sering dipakai bersama.
Kode lengkap — salin & jalankan
Simpan sebagai perpustakaan_komposisi.py, lalu python perpustakaan_komposisi.py:
class Buku:
def __init__(self, judul, penulis, tahun):
self.judul = judul
self.penulis = penulis
self.tahun = tahun
def info(self):
return f"{self.judul} · {self.penulis} ({self.tahun})"
class Ebook(Buku):
"""Inheritance tetap dipakai di mana is-a jujur."""
def __init__(self, judul, penulis, tahun, format_file="pdf"):
super().__init__(judul, penulis, tahun)
self.format_file = format_file
def info(self):
return f"{super().info()} · {self.format_file.upper()}"
class Perpustakaan:
"""Composition: punya koleksi item katalog."""
def __init__(self, nama):
self.nama = nama
self.koleksi = []
def tambah(self, item):
self.koleksi.append(item)
def daftar(self):
return [item.info() for item in self.koleksi]
def cari(self, kata):
kata = kata.lower()
return [item for item in self.koleksi if kata in item.info().lower()]
lib = Perpustakaan("Kota A")
lib.tambah(Buku("ESP32 Praktis", "Budi", 2023))
lib.tambah(Ebook("Belajar Python", "Sari", 2024, "epub"))
print("Perpustakaan:", lib.nama)
for baris in lib.daftar():
print("-", baris)
print("cari python:", [x.judul for x in lib.cari("python")])
print("Ebook adalah Buku?", isinstance(lib.koleksi[1], Buku))
print("Perpustakaan adalah Buku?", isinstance(lib, Buku))
Output yang diharapkan (kurang lebih):
Perpustakaan: Kota A
- ESP32 Praktis · Budi (2023)
- Belajar Python · Sari (2024) · EPUB
cari python: ['Belajar Python']
Ebook adalah Buku? True
Perpustakaan adalah Buku? False
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
isinstance(perpustakaan, Buku) True padahal aneh |
Warisi class domain yang salah | Ganti ke composition: simpan list Buku |
| Class induk jadi “god class” | Menjejalkan semua fitur lewat inheritance | Pecah tanggung jawab; pemilik koleksi terpisah |
| Takut inheritance sama sekali | Mengira composition selalu lebih baik | Pakai inheritance untuk is-a / kontrak ABC (#46) yang jujur |
| Copy-paste atribut antar class | Menghindari inheritance tanpa alternatif | Composition, helper, atau dataclass (artikel berikutnya) |
| Bingung dengan Encapsulation | Menyamakan “sembunyikan detail” dengan “punya object” | Encapsulation (#43) jaga akses; composition jaga struktur kepemilikan |
Latihan singkat
- Tambah method
hapus(judul)diPerpustakaanyang mengeluarkan buku darikoleksi. - Ubah
KatalogSalahmenjadi composition murni (tanpa mewarisilist), lalu bandingkanisinstance(..., list). - Jelaskan dalam dua kalimat: kenapa
Ebook(Buku)OK tapiPerpustakaan(Buku)tidak.
FAQ singkat
Apakah composition menggantikan inheritance?
Tidak. Keduanya alat. Inheritance untuk is-a/kontrak; composition untuk has-a/struktur.
Apa bedanya dengan ABC (#46)?
ABC memaksa daftar method. Composition menjawab “siapa punya object siapa” — sering dipakai bersama.
Bolehkah class mewarisi list atau dict?
Bisa secara teknis, sering menyulitkan. Prefer composition: self.items = [] lalu bungkus method yang kamu butuhkan.
Apakah “favor composition over inheritance” berarti inheritance jelek?
Tidak. Itu peringatan agar tidak warisi demi reuse buta — selaras pesan di Inheritance (#44).
Di mana atribut koleksi sebaiknya divalidasi?
Di method pemilik (tambah) atau lewat property — lihat lagi Encapsulation (#43).
Kesimpulan & langkah berikutnya
Tanya dulu: adalah atau punya? Is-a yang jujur → inheritance. Has-a / mengelola banyak object → composition. Hindari mewarisi hanya supaya atribut “ikut gratis”.
Artikel ini adalah #47 (ini) — langkah kedelapan Seri 3 setelah Abstraction & ABC (#46).
Lanjut ke artikel berikutnya — Special Methods & Dataclass: __str__, __repr__, __eq__, dan @dataclass agar object lebih nyaman dibaca dan dibandingkan. (Belum di-hyperlink sampai artikel itu live.)
Seri 3 progress: 8/10 artikel live. Kamu di langkah #47 (ini). Prasyarat: Abstraction (#46) · Inheritance (#44) · Polymorphism (#45) · Encapsulation (#43) · Attribute (#42) · Class & Object (#41) · Mengenal OOP (#40).