Menu
CoddyTech

Mixins

Termasuk bagian Pemrograman Berorientasi Objek dalam perjalanan Python di Coddy. Pelajaran 38 dari 64.

Mixin adalah jenis khusus dari pewarisan berganda yang digunakan untuk "mencampurkan" fungsionalitas tambahan ke dalam class. Mixin menyediakan method tertentu tanpa menjadi class lengkap.

Berikut contoh mixin sederhana:

class JSONSerializableMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

Mari kita uraikan fungsi mixin ini:

  • self.__dict__ - Atribut khusus ini berisi dictionary dari semua atribut object beserta nilainya
  • json.dumps() - Fungsi ini mengubah dictionary Python menjadi string berformat JSON
  • Mixin ini menambahkan fungsionalitas serialisasi JSON ke class apa pun yang mewarisinya

Sekarang, mari kita lihat cara kerjanya:

class User(JSONSerializableMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

Mixin ini menambahkan fungsionalitas JSON ke class apa pun yang mewarisinya:

user = User("Alice", "alice@example.com")
print(user.to_json())

Output:

{"name": "Alice", "email": "alice@example.com"}

Inti pemahamannya: class User sekarang memiliki method to_json() tanpa mendefinisikannya secara langsung. Mixin menambahkan fungsionalitas itu!

Buat beberapa mixin untuk fungsionalitas yang berbeda:

class PrintableMixin:
    def pretty_print(self):
        for key, value in self.__dict__.items():
            print(f"{key}: {value}")

class ComparableMixin:
    def __eq__(self, other):
        return self.__dict__ == other.__dict__

Setiap mixin mengakses self.__dict__ untuk bekerja dengan atribut object, terlepas dari class mana yang menggunakan mixin tersebut. Inilah keunggulan mixin: mixin menyediakan fungsionalitas yang dapat digunakan kembali dan berfungsi dengan atribut class apa pun.

Gabungkan beberapa mixin dalam satu class:

class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
    def __init__(self, name, price):
        self.name = name
        self.price = price

product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)

Gunakan semua fungsionalitas mixin:

print(product1.to_json())         # Dari JSONSerializableMixin
product1.pretty_print()          # Dari PrintableMixin
print(product1 == product2)      # Dari ComparableMixin

Output:

{"name": "Laptop", "price": 999}
name: Laptop
price: 999
True

Karakteristik utama mixin:

  • Tidak dimaksudkan untuk dibuat menjadi instance secara mandiri
  • Menyediakan fungsionalitas khusus yang dapat digunakan kembali
  • Biasanya tidak memiliki method __init__
  • Namanya sering diakhiri dengan "Mixin" atau "able"
  • Dapat digabungkan dengan pewarisan berganda
  • Bekerja dengan self.__dict__ atau fitur object umum lainnya agar fleksibel

Poin Penting: Mixin menyediakan cara untuk berbagi fungsionalitas di berbagai hierarki class tanpa membuat pohon pewarisan yang rumit. Mixin memungkinkan kamu untuk menambahkan kapabilitas tertentu, seperti serialisasi, perbandingan, atau pencetakan, ke class mana pun yang membutuhkannya. Hal ini mendorong penggunaan ulang kode dan membuat class tetap berfokus pada tanggung jawab utamanya.

challenge icon

Tantangan

Sedang

Dalam tantangan ini, kamu akan menerapkan sistem e-commerce sederhana menggunakan mixin dan pewarisan.

Terapkan class yang diperlukan dalam file-file ini (ikuti komentar TODO di setiap file):

  • printablemixin.py : Buat PrintableMixin dengan fungsi output berformat
  • discountmixin.py : Terapkan DiscountMixin untuk perhitungan pengurangan harga
  • shippablemixin.py : Buat ShippableMixin dengan fitur berat dan biaya pengiriman
  • product.py : Kembangkan class Product dasar dengan pewarisan mixin yang sesuai
  • physicalproduct.py : Buat class PhysicalProduct yang memperluas Product
  • digitalproduct.py : Terapkan class DigitalProduct dengan perilaku diskon khusus

Coba sendiri

from product import Product
from physicalproduct import PhysicalProduct
from digitalproduct import DigitalProduct
from printablemixin import PrintableMixin
from discountmixin import DiscountMixin
from shippablemixin import ShippableMixin

def test_basic_functionality():
    # Uji fungsi dasar semua class
    p = Product("Laptop", 1000)
    assert p.print_details() == "Product: Laptop, Price: $1000", f"Print details failed: {p.print_details()}"
    assert p.apply_discount(10) == 900, f"Discount calculation failed: {p.apply_discount(10)}"
    
    physical = PhysicalProduct("Desk", 500)
    physical.set_weight(30)
    assert physical.calculate_shipping() == 15, f"Shipping calculation failed: {physical.calculate_shipping()}"
    
    digital = DigitalProduct("Software", 200)
    assert digital.apply_discount(10) == 180, f"Digital discount failed: {digital.apply_discount(10)}"
    print("Basic functionality test passed!")

def test_edge_cases():
    # Uji kasus tepi seperti nilai nol dan negatif
    p = Product("Free Item", 0)
    assert p.apply_discount(10) == 0, f"Zero price discount failed: {p.apply_discount(10)}"
    
    p_neg = Product("Negative Item", -100)
    assert p_neg.apply_discount(10) == -90, f"Negative price discount failed: {p_neg.apply_discount(10)}"
    
    physical = PhysicalProduct("Empty Box", 10)
    physical.set_weight(0)
    assert physical.calculate_shipping() == 0, f"Zero weight shipping failed: {physical.calculate_shipping()}"
    
    physical_neg = PhysicalProduct("Anti-Gravity Item", 10)
    physical_neg.set_weight(-5)
    assert physical_neg.calculate_shipping() == -2.5, f"Negative weight shipping failed: {physical_neg.calculate_shipping()}"
    print("Edge cases test passed!")

def test_large_values():
    # Uji dengan nilai yang sangat besar
    p = Product("Expensive Item", 1000000)
    assert p.apply_discount(10) == 900000, f"Large value discount failed: {p.apply_discount(10)}"
    
    physical = PhysicalProduct("Heavy Item", 500)
    physical.set_weight(1000)
    assert physical.calculate_shipping() == 500, f"Large weight shipping failed: {physical.calculate_shipping()}"
    print("Large values test passed!")

def test_inheritance():
    # Uji hubungan pewarisan
    p = Product("Test", 100)
    physical = PhysicalProduct("Test", 100)
    digital = DigitalProduct("Test", 100)
    
    assert isinstance(p, PrintableMixin), "Product should inherit from PrintableMixin"
    assert isinstance(p, DiscountMixin), "Product should inherit from DiscountMixin"
    
    assert isinstance(physical, Product), "PhysicalProduct should inherit from Product"
    assert isinstance(physical, ShippableMixin), "PhysicalProduct should inherit from ShippableMixin"
    assert isinstance(physical, PrintableMixin), "PhysicalProduct should inherit from PrintableMixin through Product"
    assert isinstance(physical, DiscountMixin), "PhysicalProduct should inherit from DiscountMixin through Product"
    
    assert isinstance(digital, Product), "DigitalProduct should inherit from Product"
    assert isinstance(digital, PrintableMixin), "DigitalProduct should inherit from PrintableMixin through Product"
    assert isinstance(digital, DiscountMixin), "DigitalProduct should inherit from DiscountMixin through Product"
    print("Inheritance test passed!")

def test_method_overriding():
    # Uji perilaku penimpaan method
    p = Product("Regular Product", 100)
    digital = DigitalProduct("Digital Product", 100)
    
    # Harga sama, persentase diskon sama, hasil berbeda
    assert p.apply_discount(20) == 80, f"Regular discount calculation failed: {p.apply_discount(20)}"
    assert digital.apply_discount(20) == 90, f"Digital fixed discount failed: {digital.apply_discount(20)}"
    
    # Produk digital harus selalu menerapkan diskon 10% terlepas dari parameter
    assert digital.apply_discount(0) == 90, "Digital product should apply 10% discount even with 0%"
    assert digital.apply_discount(50) == 90, "Digital product should apply 10% discount even with 50%"
    print("Method overriding test passed!")

def test_polymorphism():
    # Uji perilaku polimorfik dengan list berisi berbagai jenis produk
    products = [
        Product("Regular", 100),
        PhysicalProduct("Physical", 100),
        DigitalProduct("Digital", 100)
    ]
    
    # Atur berat produk fisik
    products[1].set_weight(10)
    
    # Hasil yang diharapkan untuk apply_discount(20)
    expected_discounts = [80, 80, 90]
    
    for i, product in enumerate(products):
        # Semuanya harus memiliki method print_details
        assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
        
        # Semuanya harus memiliki method apply_discount, tetapi dengan implementasi yang berbeda
        assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
    print("Polymorphism test passed!")

def test_attribute_access():
    # Uji pola akses atribut
    p = Product("Test Product", 100)
    assert p.name == "Test Product", "Name attribute not properly set in Product"
    assert p.price == 100, "Price attribute not properly set in Product"
    
    physical = PhysicalProduct("Physical Product", 200)
    assert physical.name == "Physical Product", "Name attribute not properly set in PhysicalProduct"
    assert physical.price == 200, "Price attribute not properly set in PhysicalProduct"
    
    # Atribut weight tidak boleh ada sebelum set_weight dipanggil
    try:
        weight = physical.weight
        assert False, "Weight attribute should not exist before set_weight is called"
    except AttributeError:
        pass
    
    physical.set_weight(15)
    assert physical.weight == 15, "Weight attribute not properly set in PhysicalProduct"
    
    digital = DigitalProduct("Digital Product", 300)
    assert digital.name == "Digital Product", "Name attribute not properly set in DigitalProduct"
    assert digital.price == 300, "Price attribute not properly set in DigitalProduct"
    print("Attribute access test passed!")

# Penjalankan utama pengujian
test_case = input()

if test_case == "basic_test":
    test_basic_functionality()
elif test_case == "edge_cases":
    test_edge_cases()
elif test_case == "large_values":
    test_large_values()
elif test_case == "inheritance":
    test_inheritance()
elif test_case == "method_overriding":
    test_method_overriding()
elif test_case == "polymorphism":
    test_polymorphism()
elif test_case == "attribute_access":
    test_attribute_access()
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Pemrograman Berorientasi Objek

Latihan sendiri: Compiler Python online