Tentang Materi Ini
Sifat: OPSIONAL (Pengayaan). Materi ini tidak diujikan dan bukan bagian dari praktikum wajib. Dikerjakan bagi mahasiswa yang ingin memahami OOP secara lebih utuh, dan bernilai poin bonus.
Prasyarat: sudah menyelesaikan Pertemuan 11 (OOP Python).
Pada Pertemuan 11 kita mempelajari fondasi OOP — class, object, atribut, dan method — secukupnya untuk membangun aplikasi penjualan. Namun OOP sesungguhnya memiliki empat pilar yang sering ditanyakan di dunia kerja maupun mata kuliah lanjutan. Materi ini melengkapinya.
| Pilar | Pertanyaan yang dijawab |
|---|---|
| Encapsulation | Bagaimana melindungi data agar tidak diubah sembarangan? |
| Inheritance | Bagaimana membuat class baru tanpa menulis ulang kode? |
| Polymorphism | Bagaimana satu perintah bisa berperilaku berbeda pada objek berbeda? |
| Abstraction | Bagaimana menetapkan aturan yang wajib dipenuhi class turunan? |
Catatan penting: aplikasi penjualan yang kita bangun (Pertemuan 12–16) tidak mewajibkan keempat pilar ini. Materi ini murni untuk memperluas pemahaman, bukan mengubah project.
Tujuan
Setelah menyelesaikan materi bonus ini, mahasiswa mampu:
-
Menerapkan Encapsulation menggunakan atribut
privat dan
@property. -
Menerapkan Inheritance dengan
super()dan override method. - Menerapkan Polymorphism melalui perlakuan seragam terhadap objek berbeda.
-
Menerapkan Abstraction menggunakan
ABCdan@abstractmethod.
Persiapan
- VS Code
- Project toko-elektronik
- Virtual environment aktif (venv)
- File models/produk.py (dari Pertemuan 11)
Buat file baru oop_lanjutan.py di folder utama
project untuk seluruh latihan di materi ini.
Konsep Singkat
Empat pilar OOP menjawab masalah nyata dalam pengembangan aplikasi:
- Encapsulation (pembungkusan) — data disembunyikan dan hanya boleh diubah lewat "pintu" yang kita sediakan, sehingga aturan selalu terjaga.
- Inheritance (pewarisan) — class baru mewarisi seluruh isi class induk, lalu menambah atau mengganti bagian tertentu. Tidak perlu menyalin kode.
- Polymorphism (banyak bentuk) — objek yang berbeda dapat menanggapi perintah yang sama dengan cara masing-masing.
- Abstraction (abstraksi) — membuat kerangka/kontrak yang wajib dilengkapi oleh class turunan.
Langkah 1 - Encapsulation
Pada Pertemuan 11, atribut stok bisa diubah
langsung dari luar. Ini berbahaya:
laptop = Produk("Laptop", "Elektronik", 8500000, 10)
laptop.stok = -50 # tidak masuk akal, tetapi Python mengizinkan!
Encapsulation menutup celah tersebut. Python menandai atribut yang dilindungi dengan garis bawah:
| Penulisan | Arti | Sifat |
|---|---|---|
self.nama |
Publik | Bebas diakses |
self._harga |
Protected | Sebaiknya hanya dipakai internal (kesepakatan) |
self.__stok |
Private | Tidak bisa diakses langsung dari luar |
Gunakan @property sebagai "pintu" resmi
untuk membaca dan mengubah data:
class ProdukEnc:
def __init__(self, nama, harga, stok=0):
self.nama = nama
self._harga = harga # protected
self.__stok = stok # private
@property
def stok(self): # getter - dipakai saat MEMBACA
return self.__stok
@stok.setter
def stok(self, nilai): # setter - dipakai saat MENGUBAH
if nilai < 0:
raise ValueError("Stok tidak boleh negatif")
self.__stok = nilai
@property
def harga(self):
return self._harga
Uji:
p = ProdukEnc("Laptop", 8500000, 10)
print("Stok:", p.stok) # membaca -> lewat getter
p.stok = 7 # mengubah -> lewat setter
print("Stok:", p.stok)
try:
p.stok = -5 # ditolak setter
except ValueError as e:
print("Ditolak:", e)
try:
print(p.__stok) # akses paksa -> gagal
except AttributeError as e:
print("Akses langsung ditolak:", e)
Output:
Stok: 10
Stok: 7
Ditolak: Stok tidak boleh negatif
Akses langsung ditolak: 'ProdukEnc' object has no attribute '__stok'
Keunggulannya: pemakaian tetap sederhana (p.stok = 7, seperti atribut biasa), tetapi di baliknya ada penjaga. Data
tidak bisa dirusak dari luar.
Langkah 2 - Inheritance
Bayangkan toko mulai menjual produk digital (voucher, antivirus, software). Produk digital punya harga dan nama seperti produk biasa, tetapi tidak memiliki berat dan stoknya tidak habis.
Menyalin seluruh class Produk jelas boros. Solusinya:
pewarisan.
Class induk (sama seperti Pertemuan 11, ditambah method
info()):
class Produk:
def __init__(self, nama_produk, kategori, harga, stok=0, id_produk=None):
self.id_produk = id_produk
self.nama_produk = nama_produk
self.kategori = kategori
self.harga = harga
self.stok = stok
def nilai_persediaan(self):
return self.harga * self.stok
def info(self):
return f"{self.nama_produk} - Rp {self.harga:,}"
def kurangi_stok(self, jumlah):
if jumlah > self.stok:
raise ValueError(f"Stok {self.nama_produk} tidak cukup")
self.stok -= jumlah
return self.stok
def __str__(self):
return self.info()
Dua class turunan:
class ProdukFisik(Produk):
def __init__(self, nama_produk, kategori, harga, stok=0, berat_kg=0, id_produk=None):
super().__init__(nama_produk, kategori, harga, stok, id_produk)
self.berat_kg = berat_kg # atribut TAMBAHAN
def ongkos_kirim(self): # method TAMBAHAN
return int(self.berat_kg * 10000)
def info(self): # OVERRIDE method induk
return f"{self.nama_produk} - Rp {self.harga:,} | {self.berat_kg} kg | stok: {self.stok}"
class ProdukDigital(Produk):
def __init__(self, nama_produk, kategori, harga, ukuran_mb=0, id_produk=None):
super().__init__(nama_produk, kategori, harga, stok=999, id_produk=id_produk)
self.ukuran_mb = ukuran_mb
def ongkos_kirim(self):
return 0 # digital tidak dikirim
def kurangi_stok(self, jumlah): # OVERRIDE: stok digital tak pernah habis
return self.stok
def info(self):
return f"{self.nama_produk} (digital) - Rp {self.harga:,} | {self.ukuran_mb} MB"
Uji:
laptop = ProdukFisik("Laptop", "Elektronik", 8500000, 10, berat_kg=2.5)
antivirus = ProdukDigital("Antivirus 1thn", "Software", 350000, ukuran_mb=120)
print(laptop.info())
print(antivirus.info())
# method WARISAN dari induk tetap bisa dipakai tanpa ditulis ulang
print("Nilai persediaan laptop:", laptop.nilai_persediaan())
print("laptop juga sebuah Produk?", isinstance(laptop, Produk))
Output:
Laptop - Rp 8,500,000 | 2.5 kg | stok: 10
Antivirus 1thn (digital) - Rp 350,000 | 120 MB
Nilai persediaan laptop: 85000000
laptop juga sebuah Produk? True
Istilah penting:
| Istilah | Arti |
|---|---|
class Anak(Induk) |
Anak mewarisi seluruh atribut & method induk |
super().__init__(...) |
Memanggil constructor induk (agar tidak ditulis ulang) |
| Override | Menulis ulang method induk dengan perilaku berbeda |
isinstance(obj, Produk) |
Memeriksa apakah objek termasuk turunan Produk
|
Langkah 3 - Polymorphism
Polymorphism = "banyak bentuk". Kita memanggil method yang sama pada objek berbeda, dan masing-masing menanggapi dengan caranya sendiri.
Perhatikan: kode di bawah tidak perlu tahu jenis
tiap produk. Tidak ada satu pun if.
keranjang = [
ProdukFisik("Laptop", "Elektronik", 8500000, 10, berat_kg=2.5),
ProdukDigital("Antivirus 1thn", "Software", 350000, ukuran_mb=120),
ProdukFisik("Mouse", "Aksesoris", 150000, 50, berat_kg=0.2),
]
total_ongkir = 0
for item in keranjang:
print(f"{item.info()}")
print(f" ongkir: Rp {item.ongkos_kirim():,}")
total_ongkir += item.ongkos_kirim() # method sama, hasil berbeda
print(f"\nTotal ongkir: Rp {total_ongkir:,}")
Output:
Laptop - Rp 8,500,000 | 2.5 kg | stok: 10
ongkir: Rp 25,000
Antivirus 1thn (digital) - Rp 350,000 | 120 MB
ongkir: Rp 0
Mouse - Rp 150,000 | 0.2 kg | stok: 50
ongkir: Rp 2,000
Total ongkir: Rp 27,000
Perhatikan juga override kurangi_stok() pada
produk digital:
antivirus.kurangi_stok(5)
print("Stok digital setelah terjual 5:", antivirus.stok) # tetap 999
Mengapa ini hebat? Tanpa polymorphism, kita harus menulis:
```python
if isinstance(item, ProdukDigital):
ongkir = 0
elif isinstance(item, ProdukFisik):
ongkir = item.berat_kg * 10000
```
Setiap kali ada jenis produk baru,ifbertambah panjang. Dengan polymorphism, cukup buat class baru — kode yang memakainya tidak perlu diubah sama sekali.
Langkah 4 - Abstraction
Abstraction membuat "kontrak": sebuah kerangka class yang wajib dilengkapi turunannya. Contoh nyata: aplikasi kasir mendukung berbagai metode pembayaran, dan setiap metode wajib punya cara membayar.
Gunakan ABC (Abstract Base Class) dan
@abstractmethod:
from abc import ABC, abstractmethod
class MetodePembayaran(ABC):
"""Kerangka: setiap metode pembayaran WAJIB punya method bayar()."""
@abstractmethod
def bayar(self, jumlah):
pass # sengaja dikosongkan
def struk(self, jumlah): # method biasa, diwariskan apa adanya
return f"Dibayar Rp {jumlah:,} via {self.__class__.__name__}"
class Tunai(MetodePembayaran):
def bayar(self, jumlah):
return f"Terima tunai Rp {jumlah:,}"
class Transfer(MetodePembayaran):
def __init__(self, bank):
self.bank = bank
def bayar(self, jumlah):
return f"Transfer Rp {jumlah:,} ke bank {self.bank}"
Uji — perhatikan polymorphism ikut bekerja di sini:
daftar_metode = [Tunai(), Transfer("BCA")]
for metode in daftar_metode:
print(metode.bayar(8800000))
print(" ", metode.struk(8800000))
# class abstrak TIDAK BISA dibuat objeknya
try:
m = MetodePembayaran()
except TypeError as e:
print("\nGagal:", e)
Output:
Terima tunai Rp 8,800,000
Dibayar Rp 8,800,000 via Tunai
Transfer Rp 8,800,000 ke bank BCA
Dibayar Rp 8,800,000 via Transfer
Gagal: Can't instantiate abstract class MetodePembayaran without an implementation for abstract method 'bayar'
Gunanya: bila suatu hari ditambahkanclass QRIS(MetodePembayaran)tetapi lupa menulis methodbayar(), Python langsung menolaknya. Kesalahan tertangkap sejak awal, bukan saat aplikasi sudah dipakai pelanggan.
Referensi Fungsi Penting
Ringkasan Empat Pilar
| Pilar | Kata kunci Python | Inti |
|---|---|---|
| Encapsulation |
__atribut, @property,
@x.setter
|
Melindungi data |
| Inheritance | class Anak(Induk), super() |
Mewarisi kode |
| Polymorphism | override method | Satu perintah, banyak perilaku |
| Abstraction | ABC, @abstractmethod |
Kontrak yang wajib dipenuhi |
Penulisan yang Sering Dipakai
| Perintah | Fungsi |
|---|---|
self._x |
Atribut protected (kesepakatan) |
self.__x |
Atribut private (tidak bisa diakses dari luar) |
@property |
Menandai method sebagai getter |
@x.setter |
Menandai method sebagai setter |
super().__init__(...) |
Memanggil constructor induk |
isinstance(obj, Class) |
Memeriksa jenis objek |
obj.__class__.__name__ |
Mengambil nama class dari objek |
from abc import ABC, abstractmethod |
Membuat class abstrak |
Composition vs Inheritance
Perlu diketahui: class Transaksi di Pertemuan 11
sebenarnya sudah memakai konsep bernama
Composition — objek yang
memuat objek lain (self.items berisi
daftar ItemTransaksi).
| Konsep | Hubungan | Contoh |
|---|---|---|
| Inheritance | "adalah sebuah" (is-a) |
ProdukDigital adalah sebuah
Produk
|
| Composition | "memiliki" (has-a) |
Transaksi memiliki beberapa
ItemTransaksi
|
Keduanya sama-sama penting. Panduan praktisnya: gunakan inheritance bila memang sejenis, gunakan composition bila sekadar memuat/menggunakan.
Pengujian
Pastikan hasil praktikum sesuai berikut:
-
Encapsulation:
p.stok = -5memunculkanValueError, danp.__stoktidak bisa diakses langsung. -
Inheritance:
laptop.nilai_persediaan()bekerja walaupun method itu tidak ditulis diProdukFisik(diwarisi dariProduk). -
Polymorphism: perulangan pada keranjang
menghasilkan ongkir berbeda (Laptop 25.000,
Antivirus 0, Mouse 2.000) tanpa satu pun
if. -
Abstraction:
MetodePembayaran()langsung memunculkanTypeError.
Verifikasi cepat:
laptop = ProdukFisik("Laptop", "Elektronik", 8500000, 10, berat_kg=2.5)
antivirus = ProdukDigital("Antivirus", "Software", 350000, ukuran_mb=120)
assert isinstance(laptop, Produk)
assert laptop.nilai_persediaan() == 85000000 # method warisan
assert laptop.ongkos_kirim() == 25000
assert antivirus.ongkos_kirim() == 0 # override
antivirus.kurangi_stok(5)
assert antivirus.stok == 999 # stok digital tak berkurang
print("Semua pengujian lolos.")
Troubleshooting
| Masalah | Penyebab | Solusi |
|---|---|---|
AttributeError: object has no attribute
'__stok'
|
Mencoba mengakses atribut privat dari luar |
Gunakan property (p.stok), bukan
p.__stok
|
TypeError: __init__() missing arguments pada
class anak
|
Lupa memanggil super().__init__(...) |
Panggil constructor induk di baris pertama |
| Method induk hilang di class anak |
Lupa menulis (Induk) saat mendefinisikan class
|
Tulis class ProdukFisik(Produk): |
Can't instantiate abstract class |
Membuat objek dari class abstrak | Buat objek dari class turunannya |
| Class turunan ditolak Python | Ada @abstractmethod yang belum diisi |
Lengkapi semua method abstrak |
| Setter tidak jalan | Nama property dan setter berbeda |
@property def stok harus sepasang dengan
@stok.setter
|
Tugas Bonus
Kerjakan bila ingin memperoleh nilai tambahan.
-
Encapsulation: ubah class
Produksehinggahargamenjadi atribut privat, dengan setter yang menolak harga bernilai nol atau negatif. -
Inheritance: buat class
ProdukBundling(Produk)— paket berisi beberapa produk sekaligus, dengan atribut tambahandaftar_isi(list nama produk) dan methodjumlah_isi(). -
Polymorphism: override method
info()padaProdukBundlingsehingga menampilkan isi paketnya. Lalu masukkan ketiga jenis produk (fisik, digital, bundling) ke dalam satu list dan tampilkan seluruhnya dengan satu perulangan saja. -
Abstraction: tambahkan
class QRIS(MetodePembayaran). Buktikan bahwa bila methodbayar()sengaja tidak ditulis, Python menolak pembuatan objeknya. -
Refleksi (tulis singkat): menurut Anda, apakah
aplikasi penjualan pada Pertemuan 12–16 sebaiknya memakai
ProdukFisik/ProdukDigital, ataukah cukup satu classProduksaja? Jelaskan alasannya.
Kumpulkan berupa file .py beserta tangkapan layar
hasil menjalankannya (format PDF).
Kesimpulan
Materi bonus ini melengkapi pemahaman OOP dengan
empat pilarnya.
Encapsulation melindungi data lewat atribut
privat dan property.
Inheritance memungkinkan class baru mewarisi kode
tanpa menyalinnya. Polymorphism membuat satu
perintah dapat berperilaku berbeda pada objek berbeda, sehingga
kode bebas dari tumpukan if.
Abstraction menetapkan kontrak yang wajib
dipenuhi class turunan. Perlu diingat, aplikasi penjualan pada
Pertemuan 12–16
tetap cukup memakai class sederhana dari
Pertemuan 11 — pilar-pilar ini bukan syarat, melainkan alat yang
berguna ketika aplikasi tumbuh semakin besar dan beragam.