PBO 5 — Pewarisan (Inheritance)
Tujuan Pembelajaran:
Pada akhir pertemuan ini, mahasiswa diharapkan mampu:
- Menjelaskan konsep pewarisan (inheritance) dan manfaatnya dalam PBO.
- Mengidentifikasi hubungan “adalah sebuah” (is-a relationship) antara kelas parent dan child.
- Mengimplementasikan pewarisan di Python menggunakan kelas parent dan child.
- Memahami bagaimana metode di-override (overriding) di kelas child.
- Menggunakan fungsi
super()
untuk memanggil constructor atau metode dari kelas parent. - Membedakan antara pewarisan tunggal dan pewarisan berganda.
- Menerapkan konsep pewarisan pada studi kasus yang relevan.
A. Apa Itu Pewarisan (Inheritance)?
Dalam silsilah keluarga, Anda mungkin mewarisi beberapa karakteristik (seperti warna rambut atau mata) dari orang tua Anda. Anda adalah versi yang lebih spesifik dari “manusia” yang mewarisi sifat-sifat umum manusia, tetapi Anda juga memiliki sifat unik Anda sendiri.
Dalam PBO, pewarisan (inheritance) adalah mekanisme di mana sebuah kelas (disebut kelas anak / child class / subclass) dapat mewarisi atribut dan metode dari kelas lain (disebut kelas induk / parent class / superclass / base class).
- Kelas Induk (Parent/Superclass): Kelas yang menjadi dasar, berisi karakteristik dan perilaku umum yang akan diwarisi.
- Kelas Anak (Child/Subclass): Kelas yang mewarisi dari kelas induk. Ia mendapatkan semua atribut dan metode dari kelas induk, dan bisa menambahkan atribut/metode baru atau memodifikasi (override) yang sudah ada.
Manfaat Pewarisan:
- Reusabilitas Kode (Code Reusability): Anda tidak perlu menulis ulang kode yang sama berkali-kali. Cukup definisikan sekali di kelas induk, dan semua kelas anak bisa menggunakannya.
- Organisasi Kode: Membangun hierarki kelas membantu mengatur kode secara logis, mencerminkan hubungan “adalah sebuah” di dunia nyata.
- Ekstensibilitas (Extensibility): Mudah untuk menambahkan fungsionalitas baru dengan membuat kelas anak baru tanpa mengubah kelas induk yang sudah ada.
- Mengurangi Redundansi: Mengurangi pengulangan kode yang tidak perlu.
Hubungan “Adalah Sebuah” (Is-A Relationship)
Pewarisan paling cocok ketika ada hubungan “adalah sebuah” antara kelas-kelas.
- Seekor
Kucing
adalah sebuahHewan
. - Sebuah
Mobil
adalah sebuahKendaraan
. - Seorang
Dosen
adalah seorangPegawai
.
Ini berarti kelas Kucing
dapat mewarisi dari Hewan
, Mobil
dari Kendaraan
, dan Dosen
dari Pegawai
.
B. Implementasi Pewarisan di Python
Untuk membuat sebuah kelas anak yang mewarisi dari kelas induk di Python, kita cukup meletakkan nama kelas induk di dalam tanda kurung setelah nama kelas anak saat definisinya.
Contoh Dasar: Hewan
(Parent) dan Kucing
, Anjing
(Child)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Kelas Induk (Parent Class)
class Hewan:
def __init__(self, nama):
self.nama = nama
print(f"Objek Hewan '{self.nama}' telah dibuat.")
def bersuara(self):
print(f"{self.nama} mengeluarkan suara yang umum.")
def tidur(self):
print(f"{self.nama} sedang tidur.")
# Kelas Anak (Child Class) - Kucing mewarisi dari Hewan
class Kucing(Hewan): # Kucing "adalah sebuah" Hewan
def __init__(self, nama, ras):
# Memanggil constructor kelas induk
super().__init__(nama) # Penting! Menginisialisasi bagian Hewan dari objek Kucing
self.ras = ras
print(f"Objek Kucing '{self.nama}' (ras: {self.ras}) telah dibuat.")
# Metode baru yang spesifik untuk Kucing
def memanjat(self):
print(f"{self.nama} si kucing sedang memanjat pohon.")
# Meng-override (menimpa) metode bersuara dari kelas induk
def bersuara(self):
print(f"{self.nama} mengeong.")
# Kelas Anak (Child Class) - Anjing mewarisi dari Hewan
class Anjing(Hewan): # Anjing "adalah sebuah" Hewan
def __init__(self, nama, jenis):
super().__init__(nama) # Memanggil constructor kelas induk
self.jenis = jenis
print(f"Objek Anjing '{self.nama}' (jenis: {self.jenis}) telah dibuat.")
# Metode baru yang spesifik untuk Anjing
def menggonggong(self):
print(f"{self.nama} menggonggong: Guk! Guk!")
# Meng-override metode bersuara dari kelas induk
def bersuara(self):
print(f"{self.nama} mengaung.")
# --- Penggunaan Objek ---
print("--- Membuat Objek ---")
hewan_umum = Hewan("Binatang")
kucing_saya = Kucing("Kitty", "Persia")
anjing_teman = Anjing("Buddy", "Golden Retriever")
print("\n--- Memanggil Metode ---")
hewan_umum.bersuara() # Output: Binatang mengeluarkan suara yang umum.
kucing_saya.bersuara() # Output: Kitty mengeong. (Metode di-override)
anjing_teman.bersuara() # Output: Buddy mengaung. (Metode di-override)
kucing_saya.tidur() # Mewarisi metode tidur dari Hewan
anjing_teman.tidur() # Mewarisi metode tidur dari Hewan
kucing_saya.memanjat() # Metode spesifik Kucing
anjing_teman.menggonggong() # Metode spesifik Anjing
# print(hewan_umum.ras) # Error! Hewan umum tidak punya atribut 'ras'
C. Constructor di Pewarisan: super().__init__()
Ketika Anda mendefinisikan metode __init__
di kelas anak, metode itu akan menimpa __init__
dari kelas induk. Ini berarti __init__
dari kelas induk tidak akan dipanggil secara otomatis.
Namun, seringkali kita ingin kelas anak tetap melakukan inisialisasi yang dilakukan oleh kelas induk (misalnya, menginisialisasi atribut umum). Di sinilah fungsi super()
berperan.
super()
: Digunakan untuk memberikan akses ke metode kelas induk dari dalam kelas anak.super().__init__(...)
: Memanggil constructor dari kelas induk. Anda harus meneruskan argumen yang diperlukan oleh constructor kelas induk. Ini memastikan bahwa bagian dari objek yang terkait dengan kelas induk juga terinisialisasi dengan benar.
Mengapa Penting Memanggil super().__init__()
?
Jika Anda tidak memanggil super().__init__()
, atribut yang didefinisikan di constructor kelas induk (misalnya self.nama
di kelas Hewan
) tidak akan terinisialisasi di objek kelas anak. Ini bisa menyebabkan AttributeError
ketika Anda mencoba mengakses atribut tersebut.
Perhatikan lagi contoh Kucing
dan Anjing
di atas yang memanggil super().__init__(nama)
. Ini memastikan self.nama
diinisialisasi oleh Hewan.__init__
sebelum Kucing.__init__
atau Anjing.__init__
menambahkan atribut spesifik mereka.
D. Overriding Metode
Overriding Metode terjadi ketika sebuah kelas anak menyediakan implementasi sendiri untuk metode yang sudah didefinisikan di kelas induknya.
- Tujuan: Untuk memberikan perilaku yang lebih spesifik atau berbeda untuk kelas anak, sambil tetap mempertahankan nama metode yang sama.
- Contoh: Kelas
Hewan
memiliki metodebersuara()
.Kucing
danAnjing
memiliki cara bersuara yang spesifik (mengeong, menggonggong). Jadi, mereka meng-override metodebersuara()
untuk memberikan perilaku yang sesuai dengan jenis hewan tersebut.
Anda bisa lihat bagaimana bersuara()
di Kucing
dan Anjing
memiliki implementasi yang berbeda dari bersuara()
di Hewan
. Ketika Anda memanggil kucing_saya.bersuara()
, Python akan menjalankan metode bersuara()
yang ada di kelas Kucing
(karena ia lebih spesifik).
E. Pewarisan Tunggal vs. Pewarisan Berganda (Multiple Inheritance)
1. Pewarisan Tunggal (Single Inheritance)
- Sebuah kelas anak hanya mewarisi dari satu kelas induk. Ini adalah bentuk pewarisan yang paling umum dan sering digunakan.
- Contoh:
Kucing(Hewan)
,Anjing(Hewan)
.
2. Pewarisan Berganda (Multiple Inheritance)
- Sebuah kelas anak mewarisi dari beberapa kelas induk.
- Python mendukung pewarisan berganda, tetapi bisa menjadi kompleks dan sulit dikelola jika tidak digunakan dengan hati-hati (masalah “Diamond Problem” / Method Resolution Order - MRO).
- Kapan Digunakan: Ketika sebuah entitas menggabungkan karakteristik dari beberapa entitas lain yang tidak dapat direpresentasikan dalam hierarki tunggal yang jelas. Misalnya, sebuah
HybridCar
mungkin mewarisi dariElectricCar
danGasolineCar
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Contoh Pewarisan Berganda (Hanya konsep, perlu hati-hati dalam implementasi nyata)
class Pelari:
def lari(self):
print("Sedang berlari.")
class Perenang:
def renang(self):
print("Sedang berenang.")
class AtletTriathlon(Pelari, Perenang): # Mewarisi dari Pelari DAN Perenang
def triathlon(self):
print("Melakukan triathlon: lari, renang, bersepeda.")
atlet = AtletTriathlon()
atlet.lari()
atlet.renang()
atlet.triathlon()
Catatan: Meskipun Python mendukung pewarisan berganda, seringkali disarankan untuk menggunakan komposisi (memiliki objek dari kelas lain sebagai atribut) daripada pewarisan berganda untuk mengurangi kompleksitas, kecuali jika hubungan “adalah sebuah” sangat jelas dari beberapa induk.
F. Studi Kasus: Sistem Karyawan dengan Pewarisan
Mari kita kembangkan sistem karyawan kita menggunakan pewarisan. Kita akan memiliki kelas induk Karyawan
dan kelas anak Manajer
dan Programmer
.
Hierarki:
Karyawan
(Parent)Manajer
(Child)Programmer
(Child)
Atribut Umum Karyawan: nama
, id_karyawan
, gaji
Metode Umum Karyawan: tampilkan_info()
Atribut Spesifik Manajer: jumlah_tim
Metode Spesifik Manajer: kelola_tim()
Atribut Spesifik Programmer: bahasa_utama
Metode Spesifik Programmer: tulis_kode()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class Karyawan:
total_karyawan = 0
def __init__(self, nama, id_karyawan, gaji):
self._nama = nama
self._id_karyawan = id_karyawan
self._gaji = gaji
Karyawan.total_karyawan += 1
print(f"Karyawan '{self._nama}' dibuat.")
@property
def nama(self):
return self._nama
@property
def id_karyawan(self):
return self._id_karyawan
@property
def gaji(self):
return self._gaji
@gaji.setter
def gaji(self, nilai_baru):
if nilai_baru >= 0:
self._gaji = nilai_baru
else:
print("Gaji tidak boleh negatif.")
def tampilkan_info(self): # Metode umum
print(f"\n--- Info Karyawan ---")
print(f"Nama: {self.nama}")
print(f"ID Karyawan: {self.id_karyawan}")
print(f"Gaji: Rp {self.gaji:,}") # Format angka dengan koma
print("---------------------")
class Manajer(Karyawan):
def __init__(self, nama, id_karyawan, gaji, jumlah_tim):
super().__init__(nama, id_karyawan, gaji) # Panggil constructor Karyawan
self.jumlah_tim = jumlah_tim
print(f"Manajer '{self.nama}' dibuat.")
def kelola_tim(self): # Metode spesifik Manajer
print(f"Manajer {self.nama} sedang mengelola {self.jumlah_tim} tim.")
def tampilkan_info(self): # Override metode tampilkan_info dari Karyawan
super().tampilkan_info() # Panggil metode tampilkan_info dari Karyawan
print(f"Peran: Manajer")
print(f"Jumlah Tim: {self.jumlah_tim}")
print("---------------------")
class Programmer(Karyawan):
def __init__(self, nama, id_karyawan, gaji, bahasa_utama):
super().__init__(nama, id_karyawan, gaji) # Panggil constructor Karyawan
self.bahasa_utama = bahasa_utama
print(f"Programmer '{self.nama}' dibuat.")
def tulis_kode(self): # Metode spesifik Programmer
print(f"Programmer {self.nama} sedang menulis kode menggunakan {self.bahasa_utama}.")
def tampilkan_info(self): # Override metode tampilkan_info dari Karyawan
super().tampilkan_info() # Panggil metode tampilkan_info dari Karyawan
print(f"Peran: Programmer")
print(f"Bahasa Utama: {self.bahasa_utama}")
print("---------------------")
# --- Penggunaan ---
m_andi = Manajer("Andi", "M001", 10000000, 3)
p_budi = Programmer("Budi", "P001", 7500000, "Python")
k_cici = Karyawan("Cici", "K001", 5000000)
print(f"\nTotal karyawan di perusahaan: {Karyawan.total_karyawan}")
m_andi.tampilkan_info()
p_budi.tampilkan_info()
k_cici.tampilkan_info()
m_andi.kelola_tim()
p_budi.tulis_kode()
# Contoh akses atribut yang diwarisi
print(f"\nGaji Budi: Rp {p_budi.gaji:,}")
p_budi.gaji = 8000000 # Menggunakan setter yang diwarisi
print(f"Gaji Budi setelah naik: Rp {p_budi.gaji:,}")
# Contoh metode yang di-override
p_budi.tampilkan_info()
Penjelasan:
- Perhatikan bagaimana
Manajer
danProgrammer
tidak perlu mendefinisikan ulangnama
,id_karyawan
, dangaji
, karena mereka mewarisi dariKaryawan
. - Metode
tampilkan_info()
di-override di kelas anak untuk menambahkan informasi spesifik mereka, tetapi mereka tetap memanggilsuper().tampilkan_info()
untuk menampilkan informasi umum dari kelasKaryawan
. Ini adalah praktik terbaik. - Kita bisa memanggil metode spesifik (
kelola_tim
,tulis_kode
) hanya pada objek dari kelas yang sesuai.
G. Tugas
- Latihan Pewarisan & Enkapsulasi: Sistem Produk Toko Online
- Ambil kembali kelas
Produk
dari tugas pertemuan 4 Anda, yang sudah menerapkan enkapsulasi (getter/setter untukharga
,stok
, dannama_produk
read-only). - Buatlah dua kelas anak baru yang mewarisi dari
Produk
:ProdukElektronik
:- Memiliki atribut tambahan:
garansi_bulan
(integer). - Constructornya harus memanggil constructor
Produk
menggunakansuper()
. - Memiliki metode baru:
info_garansi()
yang mencetak informasi garansi produk elektronik tersebut. - Override metode
tampilkan_detail()
(jika ada, atau buat jika belum ada diProduk
) untuk menambahkan informasi garansi.
- Memiliki atribut tambahan:
ProdukPakaian
:- Memiliki atribut tambahan:
ukuran
(misal: “S”, “M”, “L”, “XL”) danwarna
. - Constructornya harus memanggil constructor
Produk
menggunakansuper()
. - Memiliki metode baru:
info_pakaian()
yang mencetak ukuran dan warna pakaian. - Override metode
tampilkan_detail()
untuk menambahkan informasi ukuran dan warna.
- Memiliki atribut tambahan:
- Buat minimal dua objek dari masing-masing kelas anak (
ProdukElektronik
danProdukPakaian
). - Tampilkan detail lengkap dari setiap objek menggunakan metode
tampilkan_detail()
yang sudah di-override. - Coba panggil metode spesifik dari masing-masing kelas anak (misal:
produk_elektronik.info_garansi()
,produk_pakaian.info_pakaian()
). - Demonstrasikan bagaimana enkapsulasi masih berfungsi (misalnya, coba ubah harga atau stok melalui properti/metode yang diwarisi, dan perhatikan validasinya).
- Ambil kembali kelas