Python OOP

Polymorphism

Memahami polymorphism, method overriding, dan duck typing

Apa itu Polymorphism?

Polymorphism (dari bahasa Yunani: "poly" = banyak, "morph" = bentuk) adalah kemampuan objek dari class yang berbeda untuk merespons method dengan nama yang sama dengan cara yang berbeda.

Analogi

Bayangkan tombol "Play" pada remote control. Untuk TV, tombol play menyalakan acara. Untuk AC, play menyalakan pendingin. Untuk stereo, play memutar musik. Tombol yang sama, tapi perilaku berbeda tergantung perangkat.

Jenis-jenis Polymorphism

1. Method Overriding

Method overriding terjadi ketika subclass memberikan implementasi spesifik untuk method yang sudah didefinisikan di superclass:

overriding.py
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass  # Method yang akan di-override

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

class Cow(Animal):
    def speak(self):
        return f"{self.name} says Moo!"

# Polymorphism dalam aksi
animals = [
    Dog("Buddy"),
    Cat("Whiskers"),
    Cow("Milly")
]

for animal in animals:
    print(animal.speak())
# Output:
# Buddy says Woof!
# Whiskers says Meow!
# Milly says Moo!

2. Duck Typing

Python menggunakan "duck typing": "If it walks like a duck and quacks like a duck, it must be a duck."

Python tidak peduli tipe object apa yang diberikan, selama object tersebut memiliki method yang dipanggil:

duck_typing.py
class Dog:
    def speak(self):
        return "Woof!"

class Cat:
    def speak(self):
        return "Meow!"

class Robot:
    def speak(self):
        return "Beep boop!"

# Fungsi yang menerima object apa pun dengan method speak()
def make_sound(entity):
    return entity.speak()

# Semua bisa dipanggil, tidak peduli class-nya
print(make_sound(Dog()))    # Woof!
print(make_sound(Cat()))    # Meow!
print(make_sound(Robot()))  # Beep boop!

Duck Typing vs Static Typing

Dalam bahasa dengan static typing (Java, C++), Kalian harus mendefinisikan interface atau parent class yang sama. Python lebih fleksibel dengan duck typing - yang penting memiliki method yang dibutuhkan.

Polymorphism dengan Function

Function yang Polymorphic

Buat function yang dapat bekerja dengan berbagai tipe object:

polymorphic_function.py
class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius ** 2

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

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

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

# Function polymorphic
def print_area(shape):
    """Function ini bekerja dengan object apa pun yang memiliki method area()"""
    print(f"Area: {shape.area():.2f}")

# Penggunaan
shapes = [
    Circle(5),
    Rectangle(4, 6),
    Triangle(3, 8)
]

for shape in shapes:
    print_area(shape)

Method Chaining dengan Polymorphism

method_chaining.py
class Shape:
    def __init__(self):
        self.color = "black"

    def set_color(self, color):
        self.color = color
        return self  # Return self untuk chaining

    def draw(self):
        pass  # Override di subclass

class Circle(Shape):
    def __init__(self, radius):
        super().__init__()
        self.radius = radius

    def draw(self):
        return f"Drawing {self.color} circle with radius {self.radius}"

class Square(Shape):
    def __init__(self, side):
        super().__init__()
        self.side = side

    def draw(self):
        return f"Drawing {self.color} square with side {self.side}"

# Method chaining
circle = Circle(5).set_color("red")
print(circle.draw())  # Drawing red circle with radius 5

square = Square(10).set_color("blue")
print(square.draw())  # Drawing blue square with side 10

Operator Overloading

Python memungkinkan kita untuk mendefinisikan perilaku operator (+, -, *, ==, dll) untuk class kita sendiri:

operator_overloading.py
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    # Operator + (addition)
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    # Operator - (subtraction)
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    # Operator * (scalar multiplication)
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    # Operator == (equality)
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    # Operator < (less than)
    def __lt__(self, other):
        # Compare by magnitude
        return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)

    # len() function
    def __len__(self):
        import math
        return int(math.sqrt(self.x**2 + self.y**2))

# Penggunaan
v1 = Vector(2, 3)
v2 = Vector(4, 5)

print(v1 + v2)      # Vector(6, 8)
print(v1 - v2)      # Vector(-2, -2)
print(v1 * 3)       # Vector(6, 9)
print(v1 == v2)     # False
print(v1 < v2)      # True
print(len(v1))      # 3

Common Operator Overloading Methods

OperatorMethodDeskripsi
+__add__Addition
-__sub__Subtraction
*__mul__Multiplication
/__truediv__Division
//__floordiv__Floor division
%__mod__Modulus
**__pow__Power
==__eq__Equality
!=__ne__Not equal
<__lt__Less than
>__gt__Greater than
<=__le__Less than or equal
>=__ge__Greater than or equal
len()__len__Length
str()__str__String representation

Contoh Lengkap: Payment System

Mari lihat contoh lengkap polymorphism pada sistem pembayaran:

payment_system.py
from abc import ABC, abstractmethod
from datetime import datetime

# Base class
class Payment(ABC):
    """Abstract base class untuk semua jenis payment"""

    def __init__(self, amount):
        self.amount = amount
        self.timestamp = datetime.now()
        self.status = "pending"

    @abstractmethod
    def process_payment(self):
        """Method yang harus diimplementasikan oleh subclass"""
        pass

    @abstractmethod
    def get_payment_info(self):
        """Method yang harus diimplementasikan oleh subclass"""
        pass

    def __str__(self):
        return f"{self.__class__.__name__}: Rp{self.amount:,.0f}"

# Concrete implementations
class CreditCardPayment(Payment):
    def __init__(self, amount, card_number, cvv):
        super().__init__(amount)
        self.card_number = self._mask_card_number(card_number)
        self._cvv = cvv  # Private

    def _mask_card_number(self, card_number):
        """Private method untuk mask card number"""
        return f"****-****-****-{card_number[-4:]}"

    def process_payment(self):
        # Simulasi proses pembayaran
        print(f"Processing credit card payment...")
        print(f"Card: {self.card_number}")
        self.status = "completed"
        return True

    def get_payment_info(self):
        return {
            'type': 'Credit Card',
            'card': self.card_number,
            'amount': self.amount,
            'status': self.status
        }

class BankTransferPayment(Payment):
    def __init__(self, amount, bank_name, account_number):
        super().__init__(amount)
        self.bank_name = bank_name
        self.account_number = account_number

    def process_payment(self):
        print(f"Processing bank transfer...")
        print(f"Bank: {self.bank_name}")
        print(f"Account: {self.account_number}")
        self.status = "completed"
        return True

    def get_payment_info(self):
        return {
            'type': 'Bank Transfer',
            'bank': self.bank_name,
            'account': self.account_number,
            'amount': self.amount,
            'status': self.status
        }

class EWalletPayment(Payment):
    def __init__(self, amount, wallet_type, phone_number):
        super().__init__(amount)
        self.wallet_type = wallet_type
        self.phone_number = phone_number

    def process_payment(self):
        print(f"Processing e-wallet payment...")
        print(f"Wallet: {self.wallet_type}")
        print(f"Phone: {self.phone_number}")
        self.status = "completed"
        return True

    def get_payment_info(self):
        return {
            'type': 'E-Wallet',
            'wallet': self.wallet_type,
            'phone': self.phone_number,
            'amount': self.amount,
            'status': self.status
        }

# Payment Processor - menggunakan polymorphism
class PaymentProcessor:
    """Class yang memproses berbagai jenis payment"""

    def __init__(self):
        self.payments = []

    def process(self, payment):
        """
        Method polymorphic - bekerja dengan semua jenis Payment
        tanpa perlu tahu tipe spesifiknya
        """
        print(f"\n{'='*50}")
        print(f"Processing payment: {payment}")
        print(f"{'='*50}")

        if payment.process_payment():
            self.payments.append(payment)
            print(f"Payment successful!")
            return True
        else:
            print(f"Payment failed!")
            return False

    def get_total_payments(self):
        return sum(p.amount for p in self.payments)

    def print_summary(self):
        print(f"\n{'='*50}")
        print(f"Payment Summary")
        print(f"{'='*50}")
        for i, payment in enumerate(self.payments, 1):
            info = payment.get_payment_info()
            print(f"\n{i}. {info['type']}")
            for key, value in info.items():
                if key != 'type':
                    print(f"   {key.capitalize()}: {value}")
        print(f"\nTotal: Rp{self.get_total_payments():,.0f}")
        print(f"{'='*50}")

# Penggunaan
processor = PaymentProcessor()

# Berbagai jenis payment
payments = [
    CreditCardPayment(500000, "1234567890123456", "123"),
    BankTransferPayment(750000, "BCA", "1234567890"),
    EWalletPayment(250000, "GoPay", "081234567890"),
    CreditCardPayment(1000000, "9876543210987654", "456")
]

# Process semua payment dengan method yang sama
for payment in payments:
    processor.process(payment)

# Print summary
processor.print_summary()

Polymorphism dengan Built-in Functions

Python built-in functions menggunakan polymorphism:

builtin_polymorphism.py
# len() works with different types
print(len("Hello"))        # 5 (string)
print(len([1, 2, 3]))      # 3 (list)
print(len({'a': 1, 'b': 2}))  # 2 (dict)

# Custom class dengan __len__
class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []

    def add_song(self, song):
        self.songs.append(song)

    def __len__(self):
        return len(self.songs)

playlist = Playlist("My Favorites")
playlist.add_song("Song 1")
playlist.add_song("Song 2")
print(len(playlist))  # 2

# sum() works with different iterables
print(sum([1, 2, 3]))           # 6
print(sum((1, 2, 3)))           # 6
print(sum({1, 2, 3}))           # 6

Praktik Terbaik

1. Design for Polymorphism

# Good - design dengan interface yang konsisten
class Shape:
    def area(self):
        pass

    def perimeter(self):
        pass

class Circle(Shape):
    def area(self):
        # Implementation
        pass

    def perimeter(self):
        # Implementation
        pass

# Avoid - interface yang tidak konsisten
class Circle:
    def calculate_area(self):
        pass

class Square:
    def get_area(self):  # Different method name
        pass

2. Use Abstract Base Classes

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def start(self):
        """All vehicles must implement start"""
        pass

    @abstractmethod
    def stop(self):
        """All vehicles must implement stop"""
        pass

3. Follow Liskov Substitution Principle

# Good - subclass can replace parent without breaking
class Bird:
    def move(self):
        return "Flying"

class Sparrow(Bird):
    def move(self):
        return "Flying fast"

# Avoid - subclass breaks parent's contract
class Bird:
    def fly(self):
        return "Flying"

class Penguin(Bird):
    def fly(self):
        raise Exception("Penguins can't fly!")  # Breaks contract

Latihan

  1. Buat hierarchy untuk sistem notifikasi:

    • Base class Notification dengan method send()
    • Subclass: EmailNotification, SMSNotification, PushNotification
    • Implementasikan polymorphism untuk mengirim berbagai jenis notifikasi
  2. Buat class Money dengan operator overloading untuk:

    • Addition (+)
    • Subtraction (-)
    • Comparison (==, <, >)
    • String representation
  3. Implementasikan payment gateway dengan:

    • Multiple payment methods
    • Unified processing interface
    • Transaction logging

Langkah Selanjutnya

Setelah memahami polymorphism, kita akan mempelajari Abstract Classes untuk membuat blueprint yang lebih formal untuk inheritance.