Menu
Coddy logo textTech

Miksy

Część sekcji Programowanie obiektowe ścieżki Python w Coddy. Lekcja 38 z 64.

Domieszki to szczególny rodzaj wielokrotnego dziedziczenia używany do „dodawania” klasom dodatkowych funkcjonalności. Zapewniają konkretne metody, nie będąc przy tym kompletnymi klasami.

Oto przykład prostego miksinu:

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

Przyjrzyjmy się, co robi ta domieszka:

  • self.__dict__ — ten specjalny atrybut zawiera słownik wszystkich atrybutów obiektu i ich wartości
  • json.dumps() — ta funkcja przekształca słownik Pythona w ciąg znaków w formacie JSON
  • Domieszka „dodaje” tę funkcjonalność serializacji JSON do każdej klasy, która po niej dziedziczy

Teraz zobaczmy, jak to działa:

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

Mixin dodaje funkcjonalność JSON do każdej klasy, która po nim dziedziczy:

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

Wynik:

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

Kluczowy wniosek: klasa User ma teraz metodę to_json() bez definiowania jej bezpośrednio. Mixin „wmieszał” tę funkcjonalność!

Utwórz wiele miksinów dla różnych funkcjonalności:

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__

Każdy mixin korzysta z self.__dict__, aby operować na atrybutach obiektu, niezależnie od tego, która klasa używa mixina. Na tym polega siła mixinów — zapewniają wielokrotnego użytku funkcjonalność, która działa z atrybutami dowolnej klasy.

Połącz wiele miksinów w jednej klasie:

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

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

Użyj wszystkich funkcjonalności mikserów:

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

Wynik:

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

Najważniejsze cechy miksinów:

  • Nie są przeznaczone do samodzielnego tworzenia instancji
  • Zapewniają określoną, wielokrotnego użytku funkcjonalność
  • Zazwyczaj nie mają metod __init__
  • Ich nazwy często kończą się na „Mixin” lub „able”
  • Można je łączyć z wielokrotnym dziedziczeniem
  • Korzystają z self.__dict__ lub innych powszechnych cech obiektów, aby zachować elastyczność

Kluczowa kwestia: Miksiny umożliwiają współdzielenie funkcjonalności w różnych hierarchiach klas bez tworzenia złożonych drzew dziedziczenia. Pozwalają „domieszać” określone możliwości, takie jak serializacja, porównywanie lub drukowanie, do dowolnej klasy, która ich potrzebuje. Sprzyja to ponownemu wykorzystaniu kodu i pozwala klasom skupić się na ich głównych obowiązkach.

challenge icon

Wyzwanie

Średni

W tym wyzwaniu zaimplementujesz prosty system e-commerce z wykorzystaniem mixinów i dziedziczenia.

Zaimplementuj wymagane klasy w tych plikach (postępuj zgodnie z komentarzami TODO w każdym pliku):

  • printablemixin.py — utwórz PrintableMixin z funkcjonalnością formatowania danych wyjściowych
  • discountmixin.py — zaimplementuj DiscountMixin do obliczania obniżek cen
  • shippablemixin.py — utwórz ShippableMixin z funkcjami dotyczącymi wagi i kosztów wysyłki
  • product.py — utwórz bazową klasę Product z odpowiednim dziedziczeniem po mixinach
  • physicalproduct.py — utwórz klasę PhysicalProduct rozszerzającą Product
  • digitalproduct.py — zaimplementuj klasę DigitalProduct ze specjalnym sposobem naliczania rabatu
REQUIRED OUTPUT FORMAT: [Your translated content here]

Spróbuj swoich sił

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():
    # Przetestuj podstawowe działanie wszystkich klas
    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():
    # Przetestuj przypadki brzegowe, takie jak zero i wartości ujemne
    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():
    # Przetestuj działanie z bardzo dużymi wartościami
    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():
    # Przetestuj relacje dziedziczenia
    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():
    # Przetestuj działanie nadpisywanych metod
    p = Product("Regular Product", 100)
    digital = DigitalProduct("Digital Product", 100)
    
    # Ta sama cena, ten sam procent rabatu, różne wyniki
    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)}"
    
    # Produkt cyfrowy powinien zawsze stosować rabat 10% niezależnie od parametru
    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():
    # Przetestuj zachowanie polimorficzne na liście produktów różnych typów
    products = [
        Product("Regular", 100),
        PhysicalProduct("Physical", 100),
        DigitalProduct("Digital", 100)
    ]
    
    # Ustaw wagę produktu fizycznego
    products[1].set_weight(10)
    
    # Oczekiwane wyniki dla apply_discount(20)
    expected_discounts = [80, 80, 90]
    
    for i, product in enumerate(products):
        # Wszystkie powinny mieć metodę print_details
        assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
        
        # Wszystkie powinny mieć metodę apply_discount, ale z różnymi implementacjami
        assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
    print("Polymorphism test passed!")

def test_attribute_access():
    # Testuj sposoby dostępu do atrybutów
    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"
    
    # Atrybut weight nie powinien istnieć przed wywołaniem set_weight
    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!")

# Główny moduł uruchamiający testy
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Python online