Python OOP

Inheritance (Pewarisan)

Memahami konsep pewarisan class dan method overriding

Apa itu Inheritance?

Inheritance (pewarisan) adalah mekanisme di mana sebuah class baru dapat mewarisi atribut dan method dari class yang sudah ada. Class yang diwarisi disebut parent class (superclass), dan class yang mewarisi disebut child class (subclass).

Keuntungan Inheritance

  • Code Reusability: Menghindari duplikasi kode
  • Extensibility: Mudah menambahkan fitur baru tanpa mengubah class yang ada
  • Maintainability: Perubahan di parent class otomatis berlaku untuk child class
  • Logical Hierarchy: Merepresentasikan hubungan "is-a" (adalah)

Konsep Dasar Inheritance

Terminologi

  • Parent Class / Superclass / Base Class: Class yang diwarisi
  • Child Class / Subclass / Derived Class: Class yang mewarisi
  • Method Overriding: Mengubah implementasi method dari parent class

Hubungan "is-a"

Inheritance merepresentasikan hubungan "is-a":

  • Mobil is-a Kendaraan
  • Motor is-a Kendaraan
  • Admin is-a User
  • Mahasiswa is-a Person

Implementasi Inheritance

Membuat Parent Class

Pertama, buat class dasar yang akan diwarisi:

kendaraan.py
class Kendaraan:
    """Parent class untuk semua kendaraan"""

    def __init__(self, merek, tahun):
        self.merek = merek
        self.tahun = tahun
        self.odometer = 0

    def deskripsi(self):
        return f"{self.merek} ({self.tahun})"

    def baca_odometer(self):
        return f"Kendaraan ini telah berjalan sejauh {self.odometer} kilometer"

    def update_odometer(self, km):
        if km >= self.odometer:
            self.odometer = km
            print(f"Odometer diupdate ke {km} km")
        else:
            print("Tidak dapat menurunkan odometer!")

Membuat Child Class

Buat child class yang mewarisi dari parent class menggunakan sintaks class ChildClass(ParentClass):

kendaraan.py
class Mobil(Kendaraan):
    """Child class yang mewarisi dari Kendaraan"""

    def __init__(self, merek, tahun, tipe):
        # Memanggil constructor parent class
        super().__init__(merek, tahun)
        # Atribut tambahan khusus untuk Mobil
        self.tipe = tipe
        self.bensin = 100

    # Method tambahan khusus untuk Mobil
    def isi_bensin(self, liter):
        self.bensin += liter
        return f"Bensin diisi {liter} liter. Total: {self.bensin} liter"

super() Function

super() digunakan untuk memanggil method dari parent class. Paling sering digunakan untuk memanggil __init__ parent class agar atribut parent dapat diinisialisasi.

Menggunakan Child Class

Child class memiliki akses ke semua atribut dan method dari parent class:

kendaraan.py
# Membuat instance Mobil
mobil1 = Mobil("Toyota", 2022, "SUV")

# Menggunakan method dari parent class
print(mobil1.deskripsi())  # Toyota (2022)
mobil1.update_odometer(1500)
print(mobil1.baca_odometer())  # Kendaraan ini telah berjalan sejauh 1500 kilometer

# Menggunakan method khusus Mobil
print(mobil1.isi_bensin(20))  # Bensin diisi 20 liter. Total: 120 liter

Method Overriding

Child class dapat mengganti (override) method dari parent class:

kendaraan.py
class Mobil(Kendaraan):
    def __init__(self, merek, tahun, tipe):
        super().__init__(merek, tahun)
        self.tipe = tipe
        self.bensin = 100

    # Override method deskripsi
    def deskripsi(self):
        # Memanggil method parent dengan super()
        base_desc = super().deskripsi()
        # Menambahkan informasi tambahan
        return f"{base_desc} - {self.tipe}"

    def isi_bensin(self, liter):
        self.bensin += liter
        return f"Bensin diisi {liter} liter. Total: {self.bensin} liter"

# Test method overriding
mobil1 = Mobil("Toyota", 2022, "SUV")
print(mobil1.deskripsi())  # Toyota (2022) - SUV

Contoh Lengkap

Mari lihat contoh lengkap dengan multiple child classes:

kendaraan_complete.py
# Parent Class
class Kendaraan:
    def __init__(self, merek, tahun):
        self.merek = merek
        self.tahun = tahun
        self.odometer = 0

    def deskripsi(self):
        return f"{self.merek} ({self.tahun})"

    def baca_odometer(self):
        return f"Kendaraan ini telah berjalan sejauh {self.odometer} kilometer"

    def update_odometer(self, km):
        if km >= self.odometer:
            self.odometer = km
        else:
            print("Anda tidak dapat mengubah odometer!")

    def jalan(self, km):
        self.odometer += km
        print(f"Kendaraan berjalan {km} km")

# Child Class 1: Mobil
class Mobil(Kendaraan):
    def __init__(self, merek, tahun, tipe):
        super().__init__(merek, tahun)
        self.tipe = tipe
        self.bensin = 100

    def deskripsi(self):
        base_desc = super().deskripsi()
        return f"{base_desc} - {self.tipe}"

    def isi_bensin(self, liter):
        self.bensin += liter
        return f"Bensin diisi {liter} liter. Total: {self.bensin} liter"

    def jalan(self, km):
        # Override dengan konsumsi bensin
        super().jalan(km)
        konsumsi = km / 10  # 10 km per liter
        self.bensin -= konsumsi
        print(f"Bensin tersisa: {self.bensin:.1f} liter")

# Child Class 2: Motor
class Motor(Kendaraan):
    def __init__(self, merek, tahun, cc):
        super().__init__(merek, tahun)
        self.cc = cc

    def deskripsi(self):
        return f"{self.merek} ({self.tahun}) - {self.cc}cc"

    def wheelie(self):
        return f"{self.merek} melakukan wheelie!"

# Child Class 3: Truk (warisan dari Mobil)
class Truk(Mobil):
    def __init__(self, merek, tahun, kapasitas):
        # Truk adalah SUV dalam konteks ini
        super().__init__(merek, tahun, "Truck")
        self.kapasitas = kapasitas
        self.muatan = 0

    def muat_barang(self, berat):
        if self.muatan + berat <= self.kapasitas:
            self.muatan += berat
            print(f"Barang seberat {berat} kg dimuat")
            print(f"Total muatan: {self.muatan}/{self.kapasitas} kg")
        else:
            print(f"Kapasitas tidak cukup! Maksimal {self.kapasitas} kg")

# Penggunaan
print("=== Membuat Kendaraan ===")
kendaraan1 = Kendaraan("Generic", 2020)
mobil1 = Mobil("Toyota", 2022, "SUV")
motor1 = Motor("Honda", 2021, 150)
truk1 = Truk("Isuzu", 2023, 5000)

print("\n=== Deskripsi ===")
print(kendaraan1.deskripsi())
print(mobil1.deskripsi())
print(motor1.deskripsi())
print(truk1.deskripsi())

print("\n=== Method Parent Class ===")
mobil1.update_odometer(1500)
print(mobil1.baca_odometer())

print("\n=== Method Child Class ===")
print(mobil1.isi_bensin(20))
print(motor1.wheelie())

print("\n=== Method Override ===")
mobil1.jalan(50)
motor1.jalan(30)

print("\n=== Multi-level Inheritance ===")
truk1.muat_barang(3000)
truk1.muat_barang(2500)
print(truk1.isi_bensin(50))

Multiple Inheritance

Python mendukung multiple inheritance, di mana sebuah class dapat mewarisi dari beberapa parent class:

multiple_inheritance.py
class Elektronik:
    def __init__(self, daya):
        self.daya = daya

    def nyalakan(self):
        return f"Perangkat menyala dengan daya {self.daya}W"

class Portable:
    def __init__(self, berat):
        self.berat = berat

    def bawa(self):
        return f"Membawa perangkat dengan berat {self.berat} kg"

# Multiple inheritance
class Laptop(Elektronik, Portable):
    def __init__(self, merek, daya, berat):
        Elektronik.__init__(self, daya)
        Portable.__init__(self, berat)
        self.merek = merek

    def info(self):
        return f"Laptop {self.merek}"

# Penggunaan
laptop1 = Laptop("Dell", 65, 2.5)
print(laptop1.info())
print(laptop1.nyalakan())
print(laptop1.bawa())

Method Resolution Order (MRO)

Ketika menggunakan multiple inheritance, Python menggunakan MRO untuk menentukan urutan pencarian method. Gunakan ClassName.__mro__ atau ClassName.mro() untuk melihat urutan.

Mengecek Inheritance

Python menyediakan beberapa built-in function untuk mengecek inheritance:

check_inheritance.py
class Kendaraan:
    pass

class Mobil(Kendaraan):
    pass

mobil1 = Mobil()

# isinstance() - cek apakah object adalah instance dari class
print(isinstance(mobil1, Mobil))      # True
print(isinstance(mobil1, Kendaraan))  # True (karena Mobil mewarisi Kendaraan)
print(isinstance(mobil1, str))        # False

# issubclass() - cek apakah class adalah subclass dari class lain
print(issubclass(Mobil, Kendaraan))   # True
print(issubclass(Kendaraan, Mobil))   # False
print(issubclass(Mobil, object))      # True (semua class mewarisi dari object)

# type() - mendapatkan tipe dari object
print(type(mobil1))                   # <class '__main__.Mobil'>
print(type(mobil1) == Mobil)          # True
print(type(mobil1) == Kendaraan)      # False

isinstance vs type

Gunakan isinstance() daripada type() untuk mengecek tipe object karena isinstance() memperhitungkan inheritance, sedangkan type() hanya mengecek tipe exact.

Praktik Terbaik

1. Gunakan super()

# Good - menggunakan super()
class Mobil(Kendaraan):
    def __init__(self, merek, tahun, tipe):
        super().__init__(merek, tahun)
        self.tipe = tipe

# Avoid - memanggil parent class secara eksplisit
class Mobil(Kendaraan):
    def __init__(self, merek, tahun, tipe):
        Kendaraan.__init__(self, merek, tahun)
        self.tipe = tipe

2. Liskov Substitution Principle

Child class harus dapat menggantikan parent class tanpa merusak program:

# Good - child class extends parent
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Rectangle):
    def __init__(self, size):
        super().__init__(size, size)

# Avoid - child class breaks parent's contract
class Square(Rectangle):
    def set_width(self, width):
        self.width = width
        self.height = width  # Breaks rectangle behavior

3. Favor Composition Over Inheritance

Kadang composition (has-a) lebih baik daripada inheritance (is-a):

# Inheritance (is-a)
class Engine:
    def start(self):
        return "Engine started"

class Car(Engine):  # Car is-a Engine? Not really!
    pass

# Composition (has-a) - Better!
class Engine:
    def start(self):
        return "Engine started"

class Car:
    def __init__(self):
        self.engine = Engine()  # Car has-a Engine

    def start(self):
        return self.engine.start()

Design Patterns dengan Inheritance

Template Method Pattern

template_pattern.py
from abc import ABC, abstractmethod

class DataProcessor(ABC):
    """Template method pattern"""

    def process(self):
        """Template method"""
        self.read_data()
        self.process_data()
        self.save_data()

    @abstractmethod
    def read_data(self):
        pass

    @abstractmethod
    def process_data(self):
        pass

    @abstractmethod
    def save_data(self):
        pass

class CSVProcessor(DataProcessor):
    def read_data(self):
        print("Reading from CSV...")

    def process_data(self):
        print("Processing CSV data...")

    def save_data(self):
        print("Saving to database...")

class JSONProcessor(DataProcessor):
    def read_data(self):
        print("Reading from JSON...")

    def process_data(self):
        print("Processing JSON data...")

    def save_data(self):
        print("Saving to file...")

# Penggunaan
csv_proc = CSVProcessor()
csv_proc.process()

json_proc = JSONProcessor()
json_proc.process()

Latihan

  1. Buat class hierarchy untuk sistem e-commerce:

    • Product (parent)
    • PhysicalProduct (child dengan atribut weight, dimensions)
    • DigitalProduct (child dengan atribut file_size, download_link)
  2. Implementasikan method overriding untuk calculate_shipping_cost() yang berbeda untuk physical dan digital product

  3. Tambahkan class User dan PremiumUser dengan method untuk menghitung diskon

  4. Gunakan isinstance() untuk memberikan diskon berbeda berdasarkan tipe user

Langkah Selanjutnya

Setelah memahami inheritance, kita akan mempelajari Encapsulation untuk melindungi data dalam class dengan access modifiers.