32. Memperluas Class dengan Pewarisan dan Polimorfisme
Di Bab 30, kita belajar cara membuat class kita sendiri untuk memodelkan konsep dunia nyata. Kita membangun class seperti BankAccount dan Student yang menggabungkan data dan perilaku menjadi satu. Tapi apa yang terjadi kalau kamu perlu membuat class baru yang mirip dengan class yang sudah ada, tetapi dengan beberapa perbedaan atau tambahan?
Pewarisan (inheritance) adalah mekanisme Python untuk membuat class baru berdasarkan class yang sudah ada. Alih-alih menyalin dan menempelkan kode, kamu bisa membuat subclass yang secara otomatis mendapatkan semua atribut dan method dari class induk (juga disebut base class atau superclass), lalu menambahkan atau memodifikasi apa yang kamu butuhkan.
Bab ini membahas bagaimana pewarisan memungkinkan kamu membangun hierarki class yang saling berhubungan, bagaimana menyesuaikan perilaku yang diwariskan, dan bagaimana polimorfisme (polymorphism) memungkinkan class yang berbeda dipakai secara saling menggantikan ketika mereka berbagi interface yang sama.
32.1) Membuat Subclass dari Class yang Sudah Ada
32.1.1) Sintaks Dasar Pewarisan
Pewarisan memungkinkan kamu membuat class baru (disebut subclass atau child class) berdasarkan class yang sudah ada (disebut class induk atau base class). Subclass secara otomatis mewarisi semua method dan atribut dari class induk.
Saat kamu membuat subclass, kamu menyebutkan class induk di dalam tanda kurung setelah nama class.
# Class induk
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
# Subclass yang mewarisi dari Animal
class Dog(Animal):
pass # Belum perlu kode tambahan
# Membuat instance Dog
buddy = Dog("Buddy")
print(buddy.speak()) # Output: Buddy makes a sound
print(buddy.name) # Output: BuddyWalaupun Dog tidak punya kode sendiri (hanya pass), ia mewarisi semuanya dari Animal. Class Dog secara otomatis memiliki method __init__ dan method speak dari class induknya.
32.1.2) Kenapa Pewarisan Itu Penting
Pewarisan menyelesaikan masalah pemrograman yang umum: duplikasi kode. Bayangkan kamu sedang membangun sistem untuk mengelola berbagai tipe karyawan:
# Tanpa pewarisan - banyak duplikasi
class FullTimeEmployee:
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self.salary = salary
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
class PartTimeEmployee:
def __init__(self, name, employee_id, hourly_rate):
self.name = name
self.employee_id = employee_id
self.hourly_rate = hourly_rate
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"Perhatikan bagaimana name, employee_id, dan get_info() terduplikasi. Dengan pewarisan, kita bisa menghilangkan duplikasi ini:
# Dengan pewarisan - kode bersama di class induk
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
class FullTimeEmployee(Employee):
def __init__(self, name, employee_id, salary):
Employee.__init__(self, name, employee_id) # Memanggil __init__ milik induk
# Catatan: Kita akan mempelajari cara yang lebih baik untuk ini dengan super() di Bagian 32.3
self.salary = salary
class PartTimeEmployee(Employee):
def __init__(self, name, employee_id, hourly_rate):
Employee.__init__(self, name, employee_id) # Memanggil __init__ milik induk
self.hourly_rate = hourly_rate
# Kedua subclass mewarisi get_info()
alice = FullTimeEmployee("Alice", "E001", 75000)
bob = PartTimeEmployee("Bob", "E002", 25)
print(alice.get_info()) # Output: Alice (ID: E001)
print(bob.get_info()) # Output: Bob (ID: E002)Sekarang atribut dan method yang umum berada di Employee, dan setiap subclass hanya mendefinisikan apa yang membuatnya unik.
32.1.3) Menambahkan Method Baru ke Subclass
Subclass bisa menambahkan method mereka sendiri yang tidak dimiliki induknya:
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def get_description(self):
return f"{self.brand} {self.model}"
class Car(Vehicle):
def __init__(self, brand, model, num_doors):
Vehicle.__init__(self, brand, model)
self.num_doors = num_doors
def honk(self): # Method baru khusus untuk Car
return "Beep beep!"
class Motorcycle(Vehicle):
def __init__(self, brand, model, has_sidecar):
Vehicle.__init__(self, brand, model)
self.has_sidecar = has_sidecar
def rev_engine(self): # Method baru khusus untuk Motorcycle
return "Vroom vroom!"
# Setiap subclass punya method dari induknya ditambah miliknya sendiri
my_car = Car("Toyota", "Camry", 4)
print(my_car.get_description()) # Output: Toyota Camry
print(my_car.honk()) # Output: Beep beep!
my_bike = Motorcycle("Harley", "Sportster", False)
print(my_bike.get_description()) # Output: Harley Sportster
print(my_bike.rev_engine()) # Output: Vroom vroom!Class Car memiliki get_description() (diwariskan) dan honk() (miliknya sendiri). Class Motorcycle memiliki get_description() (diwariskan) dan rev_engine() (miliknya sendiri).
32.1.4) Menambahkan Atribut Baru ke Subclass
Subclass juga bisa menambahkan atribut instance mereka sendiri. Biasanya kamu melakukan ini di method __init__ milik subclass:
class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
class SavingsAccount(BankAccount):
def __init__(self, account_number, balance, interest_rate):
BankAccount.__init__(self, account_number, balance)
self.interest_rate = interest_rate # Atribut baru
def apply_interest(self): # Method baru yang memakai atribut baru
interest = self.balance * self.interest_rate
self.balance += interest
return interest
# SavingsAccount punya semua atribut BankAccount plus interest_rate
savings = SavingsAccount("SA001", 1000, 0.03)
savings.deposit(500) # Method yang diwariskan
print(f"Balance: ${savings.balance}") # Output: Balance: $1500
interest_earned = savings.apply_interest()
print(f"Interest earned: ${interest_earned:.2f}") # Output: Interest earned: $45.00
print(f"New balance: ${savings.balance:.2f}") # Output: New balance: $1545.00SavingsAccount memiliki account_number dan balance dari BankAccount, ditambah atribut interest_rate miliknya sendiri.
32.1.5) Beberapa Tingkat Pewarisan
Class bisa mewarisi dari class yang juga mewarisi dari class lain, sehingga membentuk hierarki pewarisan:
class LivingThing:
def __init__(self, name):
self.name = name
def is_alive(self):
return True
class Animal(LivingThing):
def __init__(self, name, species):
LivingThing.__init__(self, name)
self.species = species
def move(self):
return f"{self.name} is moving"
class Dog(Animal):
def __init__(self, name, breed):
Animal.__init__(self, name, "Dog")
self.breed = breed
def bark(self):
return f"{self.name} says: Woof!"
# Dog mewarisi dari Animal, yang mewarisi dari LivingThing
max_dog = Dog("Max", "Golden Retriever")
# Method dari ketiga level semuanya berfungsi
print(max_dog.is_alive()) # Output: True (dari LivingThing)
print(max_dog.move()) # Output: Max is moving (dari Animal)
print(max_dog.bark()) # Output: Max says: Woof! (dari Dog)
# Atribut dari ketiga level semuanya ada
print(max_dog.name) # Output: Max (dari LivingThing)
print(max_dog.species) # Output: Dog (dari Animal)
print(max_dog.breed) # Output: Golden Retriever (dari Dog)Dog mewarisi dari Animal, yang mewarisi dari LivingThing. Ini berarti Dog punya akses ke method dan atribut dari kedua class induk tersebut.
32.2) Meng-override Method di Subclass
32.2.1) Apa Arti Method Overriding
Terkadang sebuah subclass perlu mengubah cara kerja method yang diwariskan. Method overriding berarti mendefinisikan sebuah method di subclass dengan nama yang sama seperti method di class induk. Saat kamu memanggil method itu pada instance subclass, Python memakai versi milik subclass, bukan milik induk.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def speak(self): # Meng-override method speak milik induk
return f"{self.name} says: Woof!"
class Cat(Animal):
def speak(self): # Override dengan perilaku yang berbeda
return f"{self.name} says: Meow!"
# Setiap class punya versinya sendiri untuk speak()
generic_animal = Animal("Generic")
print(generic_animal.speak()) # Output: Generic makes a sound
buddy = Dog("Buddy")
print(buddy.speak()) # Output: Buddy says: Woof!
whiskers = Cat("Whiskers")
print(whiskers.speak()) # Output: Whiskers says: Meow!Saat kamu memanggil buddy.speak(), Python mencari method speak di class Dog terlebih dulu, karena buddy adalah instance Dog. Karena Dog mendefinisikan method speak sendiri, Python memakai versi itu. Kalau Dog tidak punya method speak, Python kemudian akan mencari di class induk Animal dan memakai versi dari sana.
Urutan pencarian ini—dimulai dari class milik instance, lalu naik ke class induk—adalah cara kerja method overriding dan bagaimana subclass menyesuaikan perilaku yang diwariskan.
32.2.2) Kenapa Meng-override Method?
Method overriding memungkinkan kamu membuat versi spesifik dari perilaku yang umum. Pertimbangkan hierarki shape:
class Shape:
def __init__(self, name):
self.name = name
def area(self):
return 0 # Implementasi default
def describe(self):
return f"{self.name} with area {self.area()}"
class Rectangle(Shape):
def __init__(self, width, height):
Shape.__init__(self, "Rectangle")
self.width = width
self.height = height
def area(self): # Override dengan perhitungan khusus rectangle
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
Shape.__init__(self, "Circle")
self.radius = radius
def area(self): # Override dengan perhitungan khusus circle
return 3.14159 * self.radius ** 2
# Setiap shape menghitung area secara berbeda
rect = Rectangle(5, 3)
print(rect.describe()) # Output: Rectangle with area 15
circle = Circle(4)
print(circle.describe()) # Output: Circle with area 50.26544Method describe() diwariskan oleh kedua subclass dan bekerja dengan benar karena setiap subclass menyediakan implementasi area() miliknya sendiri.
Saat kamu memanggil rect.describe(), method describe() yang diwariskan dijalankan, tetapi self merujuk ke instance Rectangle. Jadi ketika describe() memanggil self.area(), Python mencari area() di class Rectangle terlebih dulu dan menemukan versi yang dioverride.
32.2.3) Meng-override __init__ dan Memanggil Inisialisasi Induk
Saat kamu meng-override __init__, biasanya kamu perlu memanggil __init__ milik induk untuk memastikan inisialisasi induk tetap terjadi:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I'm {self.name}, {self.age} years old"
class Student(Person):
def __init__(self, name, age, student_id, major):
# Panggil __init__ milik induk untuk mengatur name dan age
Person.__init__(self, name, age)
# Lalu set atribut khusus student
self.student_id = student_id
self.major = major
alice = Student("Alice", 20, "S12345", "Computer Science")
print(alice.name) # Output: Alice
print(alice.student_id) # Output: S12345Perhatikan bagaimana Student.__init__ pertama-tama memanggil Person.__init__(self, name, age) untuk menginisialisasi atribut milik class induk, lalu menambahkan atributnya sendiri.
32.3) Menggunakan super() untuk Mengakses Perilaku Induk
32.3.1) Apa yang Dilakukan super()
Di bagian sebelumnya, kita memanggil method induk secara eksplisit: ParentClass.method(self, ...). Python menyediakan cara yang lebih rapi: fungsi super(). super() mengembalikan objek sementara yang memungkinkan kamu memanggil method dari class induk tanpa menyebut nama induk secara eksplisit.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Lebih rapi daripada Animal.__init__(self, name)
self.breed = breed
def speak(self):
parent_sound = super().speak() # Memanggil speak() milik induk
return f"{parent_sound} - specifically, Woof!"
buddy = Dog("Buddy", "Labrador")
print(buddy.speak())
# Output: Buddy makes a sound - specifically, Woof!Menggunakan super() punya beberapa keuntungan:
- Kamu tidak perlu menyebut nama class induk secara eksplisit
- Ia bekerja dengan benar pada multiple inheritance (dibahas nanti)
- Ini membuat kode lebih mudah dirawat jika kamu mengubah class induk
32.3.2) Menggunakan super() di __init__
Penggunaan super() yang paling umum adalah memanggil __init__ milik induk:
class Employee:
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
self.is_active = True
def deactivate(self):
self.is_active = False
class Manager(Employee):
def __init__(self, name, employee_id, department):
super().__init__(name, employee_id) # Inisialisasi atribut induk
self.department = department
self.team = [] # Atribut khusus Manager
def add_team_member(self, employee):
self.team.append(employee)
# Manager mendapatkan semua atribut Employee plus miliknya sendiri
sarah = Manager("Sarah", "M001", "Engineering")
print(sarah.name) # Output: Sarah
print(sarah.is_active) # Output: True
print(sarah.department) # Output: EngineeringDengan memanggil super().__init__(name, employee_id), class Manager memastikan bahwa semua logika inisialisasi dari Employee berjalan, termasuk mengatur is_active menjadi True.
32.3.3) Memperluas Method Induk dengan super()
Kamu bisa memakai super() untuk memperluas method induk alih-alih menggantinya sepenuhnya:
class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number
self.balance = balance
self.transaction_count = 0
def deposit(self, amount):
self.balance += amount
self.transaction_count += 1
return self.balance
class CheckingAccount(BankAccount):
def __init__(self, account_number, balance, overdraft_limit):
super().__init__(account_number, balance)
self.overdraft_limit = overdraft_limit
def deposit(self, amount):
was_overdrawn = self.balance < 0
# Panggil deposit milik induk untuk menangani logika dasar
new_balance = super().deposit(amount)
# Tambahkan perilaku khusus checking
if was_overdrawn and new_balance >= 0:
print("Account is no longer overdrawn")
return new_balance
checking = CheckingAccount("C001", -50, 100)
checking.deposit(75)
# Output: Account is no longer overdrawn
print(f"Balance: ${checking.balance}") # Output: Balance: $25
print(f"Transactions: {checking.transaction_count}") # Output: Transactions: 1Method CheckingAccount.deposit() memanggil super().deposit(amount) untuk menangani logika deposit dasar (mengupdate balance dan jumlah transaksi), lalu menambahkan pengecekan sendiri untuk status overdraft.
32.3.4) Kapan Menggunakan super() vs Pemanggilan Induk Secara Langsung
Gunakan super() dalam kebanyakan kasus:
class Vehicle:
def __init__(self, brand):
self.brand = brand
class Car(Vehicle):
def __init__(self, brand, model):
super().__init__(brand) # Direkomendasikan
self.model = modelGunakan pemanggilan induk secara langsung saat kamu perlu memanggil induk tertentu dalam skenario multiple inheritance (dibahas nanti) atau saat kamu ingin eksplisit tentang induk mana yang kamu panggil:
class Car(Vehicle):
def __init__(self, brand, model):
Vehicle.__init__(self, brand) # Eksplisit, tapi kurang fleksibel
self.model = modelUntuk single inheritance (satu induk), super() hampir selalu pilihan yang lebih baik.
32.3.5) super() dengan Method Lain
Kamu bisa menggunakan super() dengan method apa pun, bukan hanya __init__:
class TextProcessor:
def process(self, text):
# Pemrosesan dasar: hapus spasi di awal/akhir
return text.strip()
class UppercaseProcessor(TextProcessor):
def process(self, text):
# Pertama lakukan pemrosesan milik induk
processed = super().process(text)
# Lalu tambahkan konversi ke huruf besar
return processed.upper()
class PrefixProcessor(UppercaseProcessor):
def __init__(self, prefix):
self.prefix = prefix
def process(self, text):
# Pertama lakukan pemrosesan milik induk (yang juga memanggil induknya)
processed = super().process(text)
# Lalu tambahkan prefix
return f"{self.prefix}: {processed}"
processor = PrefixProcessor("ALERT")
result = processor.process(" system error ")
print(result) # Output: ALERT: SYSTEM ERROR32.4) Polimorfisme: Bekerja dengan Class yang Kompatibel
32.4.1) Apa Arti Polimorfisme
Polimorfisme (dari bahasa Yunani: "many forms") adalah kemampuan untuk memperlakukan objek dari class yang berbeda dengan cara yang sama jika mereka menyediakan method yang sama.
Di Python, jika beberapa class memiliki method dengan nama yang sama, kamu bisa memanggil method itu tanpa mengetahui class persis dari objek tersebut.
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says: Woof!"
class Cat:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says: Meow!"
class Bird:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says: Tweet!"
# Fungsi yang bekerja dengan objek apa pun yang punya method speak()
def make_animal_speak(animal):
print(animal.speak())
# Bekerja dengan class yang berbeda
buddy = Dog("Buddy")
whiskers = Cat("Whiskers")
tweety = Bird("Tweety")
make_animal_speak(buddy) # Output: Buddy says: Woof!
make_animal_speak(whiskers) # Output: Whiskers says: Meow!
make_animal_speak(tweety) # Output: Tweety says: Tweet!Fungsi make_animal_speak() tidak peduli class apa yang menjadi parameter animal—ia hanya butuh objek tersebut memiliki method speak(). Ini adalah polimorfisme dalam praktik.
32.4.2) Polimorfisme dengan Pewarisan
Polimorfisme sangat kuat saat dikombinasikan dengan pewarisan, ketika subclass meng-override method milik induk:
class PaymentMethod:
def process_payment(self, amount):
return f"Processing ${amount:.2f}"
class CreditCard(PaymentMethod):
def __init__(self, card_number):
self.card_number = card_number
def process_payment(self, amount):
return f"Charging ${amount:.2f} to credit card ending in {self.card_number[-4:]}"
class PayPal(PaymentMethod):
def __init__(self, email):
self.email = email
def process_payment(self, amount):
return f"Sending ${amount:.2f} via PayPal to {self.email}"
class BankTransfer(PaymentMethod):
def __init__(self, account_number):
self.account_number = account_number
def process_payment(self, amount):
return f"Transferring ${amount:.2f} to account {self.account_number}"
# Fungsi yang bekerja dengan PaymentMethod apa pun
def complete_purchase(payment_method, amount):
print(payment_method.process_payment(amount))
print("Purchase complete!")
# Semua metode pembayaran bekerja dengan fungsi yang sama
credit = CreditCard("1234567890123456")
paypal = PayPal("user@example.com")
bank = BankTransfer("9876543210")
complete_purchase(credit, 99.99)
# Output: Charging $99.99 to credit card ending in 3456
# Output: Purchase complete!
complete_purchase(paypal, 49.50)
# Output: Sending $49.50 via PayPal to user@example.com
# Output: Purchase complete!
complete_purchase(bank, 199.00)
# Output: Transferring $199.00 to account 9876543210
# Output: Purchase complete!Fungsi complete_purchase() bekerja dengan subclass PaymentMethod mana pun. Setiap subclass menyediakan implementasinya sendiri untuk process_payment(), tetapi fungsi tersebut tidak perlu tahu class spesifik mana yang sedang dipakai.
32.4.3) Duck Typing: "Kalau Jalannya Seperti Bebek..."
Polimorfisme di Python tidak mengharuskan class saling berhubungan lewat pewarisan. Ini disebut duck typing: "If it walks like a duck and quacks like a duck, then it is a duck." Dengan kata lain, Python peduli pada apa yang bisa dilakukan sebuah objek (method-nya), bukan apa dirinya (class-nya). Jika sebuah objek memiliki method yang kamu butuhkan, kamu bisa memakainya, terlepas dari hierarki class-nya.
class FileWriter:
def __init__(self, filename):
self.filename = filename
def write(self, data):
print(f"Writing to {self.filename}: {data}")
class DatabaseWriter:
def __init__(self, table_name):
self.table_name = table_name
def write(self, data):
print(f"Inserting into {self.table_name}: {data}")
class ConsoleWriter:
def write(self, data):
print(f"Console output: {data}")
# Fungsi yang bekerja dengan objek apa pun yang punya method write()
def save_data(writer, data):
writer.write(data)
# Ketiga class bekerja, walaupun tidak saling berhubungan
file_writer = FileWriter("data.txt")
db_writer = DatabaseWriter("users")
console_writer = ConsoleWriter()
save_data(file_writer, "User data")
# Output: Writing to data.txt: User data
save_data(db_writer, "User data")
# Output: Inserting into users: User data
save_data(console_writer, "User data")
# Output: Console output: User dataTidak ada satu pun dari class ini yang mewarisi dari induk yang sama, tetapi semuanya bekerja dengan save_data() karena semuanya punya method write(). Ini adalah duck typing—fungsi tidak peduli pada class, hanya pada interface (method yang tersedia).
32.4.4) Contoh Praktis: Sistem Plugin
Polimorfisme memungkinkan sistem yang fleksibel dan mudah diperluas. Berikut sistem plugin sederhana untuk pemrosesan data:
class DataProcessor:
def process(self, data):
return data # Implementasi dasar tidak melakukan apa pun
class UppercaseProcessor(DataProcessor):
def process(self, data):
return data.upper()
class ReverseProcessor(DataProcessor):
def process(self, data):
return data[::-1]
class RemoveSpacesProcessor(DataProcessor):
def process(self, data):
return data.replace(" ", "")
class DataPipeline:
def __init__(self):
self.processors = []
def add_processor(self, processor):
self.processors.append(processor)
def run(self, data):
result = data
for processor in self.processors:
result = processor.process(result) # Pemanggilan polimorfik
return result
# Membangun pipeline pemrosesan
pipeline = DataPipeline()
pipeline.add_processor(UppercaseProcessor())
pipeline.add_processor(RemoveSpacesProcessor())
pipeline.add_processor(ReverseProcessor())
# Memproses data melalui pipeline
input_data = "Hello World"
output = pipeline.run(input_data)
print(f"Input: {input_data}") # Output: Input: Hello World
print(f"Output: {output}") # Output: Output: DLROWOLLEHDataPipeline tidak perlu tahu processor spesifik apa yang dimilikinya—ia hanya memanggil process() pada masing-masing. Kamu bisa dengan mudah menambahkan tipe processor baru tanpa mengubah kode pipeline.
32.5) Mengecek Tipe dan Relasi Class (isinstance, issubclass)
32.5.1) Mengecek Tipe Instance dengan isinstance()
Terkadang kamu perlu mengecek apakah sebuah objek adalah instance dari class tertentu. Fungsi isinstance() melakukan ini:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
buddy = Dog()
whiskers = Cat()
# Mengecek apakah sebuah objek merupakan instance dari sebuah class
print(isinstance(buddy, Dog)) # Output: True
print(isinstance(buddy, Animal)) # Output: True (Dog mewarisi dari Animal)
print(isinstance(buddy, Cat)) # Output: False
print(isinstance(whiskers, Cat)) # Output: True
print(isinstance(whiskers, Animal)) # Output: True
print(isinstance(whiskers, Dog)) # Output: FalsePerhatikan bahwa isinstance(buddy, Animal) menghasilkan True walaupun buddy adalah instance Dog. Ini karena Dog mewarisi dari Animal, jadi instance Dog juga dianggap sebagai instance Animal.
32.5.2) Kenapa isinstance() Menghormati Pewarisan
Fungsi isinstance() mengecek seluruh rantai pewarisan:
class Vehicle:
pass
class Car(Vehicle):
pass
class ElectricCar(Car):
pass
tesla = ElectricCar()
# Mengecek semua level pewarisan
print(isinstance(tesla, ElectricCar)) # Output: True
print(isinstance(tesla, Car)) # Output: True
print(isinstance(tesla, Vehicle)) # Output: True
print(isinstance(tesla, str)) # Output: FalseObjek tesla adalah instance dari ElectricCar, tetapi juga instance dari Car dan Vehicle karena pewarisan.
32.5.3) Mengecek Beberapa Tipe Sekaligus
Kamu bisa mengecek apakah sebuah objek adalah instance dari salah satu dari beberapa class dengan mengoper sebuah tuple:
class Dog:
pass
class Cat:
pass
class Bird:
pass
def is_pet(animal):
return isinstance(animal, (Dog, Cat, Bird))
buddy = Dog()
whiskers = Cat()
tweety = Bird()
rock = "just a rock"
print(is_pet(buddy)) # Output: True
print(is_pet(whiskers)) # Output: True
print(is_pet(tweety)) # Output: True
print(is_pet(rock)) # Output: FalseIni lebih ringkas daripada menulis isinstance(animal, Dog) or isinstance(animal, Cat) or isinstance(animal, Bird).
32.5.4) Mengecek Relasi Class dengan issubclass()
Fungsi issubclass() mengecek apakah sebuah class merupakan subclass dari class lainnya:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class Poodle(Dog):
pass
# Mengecek relasi antar class
print(issubclass(Dog, Animal)) # Output: True
print(issubclass(Cat, Animal)) # Output: True
print(issubclass(Poodle, Dog)) # Output: True
print(issubclass(Poodle, Animal)) # Output: True (pewarisan tidak langsung)
print(issubclass(Dog, Cat)) # Output: False
# Sebuah class dianggap subclass dari dirinya sendiri
print(issubclass(Dog, Dog)) # Output: TruePerlu diingat issubclass() bekerja untuk class, bukan instance. Gunakan isinstance() untuk instance dan issubclass() untuk class.
32.5.5) Contoh Penggunaan Praktis untuk Pengecekan Tipe
Pengecekan tipe berguna saat kamu butuh perilaku berbeda untuk tipe yang berbeda:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
class Contractor:
def __init__(self, name, hourly_rate, hours):
self.name = name
self.hourly_rate = hourly_rate
self.hours = hours
def calculate_payment(worker):
# Saat menggunakan isinstance() dengan pewarisan, cek subclass sebelum class induk
if isinstance(worker, Manager): # Cek Manager dulu
return worker.salary + worker.bonus
elif isinstance(worker, Employee): # Lalu cek Employee (class induk)
return worker.salary
elif isinstance(worker, Contractor):
return worker.hourly_rate * worker.hours
else:
return 0
alice = Employee("Alice", 50000)
bob = Manager("Bob", 70000, 10000)
charlie = Contractor("Charlie", 50, 160)
print(f"Alice's payment: ${calculate_payment(alice)}") # Output: Alice's payment: $50000
print(f"Bob's payment: ${calculate_payment(bob)}") # Output: Bob's payment: $80000
print(f"Charlie's payment: ${calculate_payment(charlie)}")# Output: Charlie's payment: $8000Namun, dalam banyak kasus, polimorfisme (membuat setiap class mengimplementasikan method yang sama) lebih baik daripada pengecekan tipe:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def calculate_payment(self):
return self.salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def calculate_payment(self):
return self.salary + self.bonus
class Contractor:
def __init__(self, name, hourly_rate, hours):
self.name = name
self.hourly_rate = hourly_rate
self.hours = hours
def calculate_payment(self):
return self.hourly_rate * self.hours
# Tidak perlu pengecekan tipe - polimorfisme yang menanganinya
workers = [
Employee("Alice", 50000),
Manager("Bob", 70000, 10000),
Contractor("Charlie", 50, 160)
]
for worker in workers:
payment = worker.calculate_payment() # Pemanggilan polimorfik
print(f"{worker.name}'s payment: ${payment}")Output:
Alice's payment: $50000
Bob's payment: $80000
Charlie's payment: $8000Pendekatan polimorfik ini lebih fleksibel dan lebih mudah diperluas dengan tipe worker baru. Kamu tidak perlu memodifikasi kode pemanggil saat kamu menambahkan class worker baru—cukup pastikan ia punya method calculate_payment().
Pewarisan dan polimorfisme adalah alat yang kuat untuk mengorganisir kode dan membuat sistem yang fleksibel serta mudah diperluas. Dengan membuat subclass, kamu bisa menggunakan ulang kode yang sudah ada sambil menambahkan atau memodifikasi perilaku. Dengan meng-override method, kamu bisa menyesuaikan cara kerja subclass. Dan dengan menggunakan polimorfisme, kamu bisa menulis kode yang bekerja dengan banyak class berbeda melalui interface yang sama.
Kuncinya adalah menggunakan fitur-fitur ini secara bijak:
- Gunakan pewarisan ketika ada hubungan "is-a" yang benar-benar nyata (seekor
Dogadalah sebuahAnimal) - Override method untuk membuat perilaku lebih spesifik, bukan untuk mengubah total apa yang dilakukan sebuah class
- Gunakan
super()untuk memperluas perilaku induk alih-alih menggantinya sepenuhnya - Utamakan polimorfisme (method yang sama) daripada pengecekan tipe jika memungkinkan
Saat kamu membangun program yang lebih besar, teknik OOP ini akan membantu kamu membuat kode yang lebih mudah dipahami, dirawat, dan dikembangkan.