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ścijson.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 = emailMixin 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 ComparableMixinWynik:
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
TrueNajważ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.
Wyzwanie
ŚredniW 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ściowychdiscountmixin.py— zaimplementuj DiscountMixin do obliczania obniżek censhippablemixin.py— utwórz ShippableMixin z funkcjami dotyczącymi wagi i kosztów wysyłkiproduct.py— utwórz bazową klasę Product z odpowiednim dziedziczeniem po mixinachphysicalproduct.py— utwórz klasę PhysicalProduct rozszerzającą Productdigitalproduct.py— zaimplementuj klasę DigitalProduct ze specjalnym sposobem naliczania rabatu
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()Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektyParametr selfMetodyAtrybutyMetoda konstruktora (__init__)Powtórzenie – prosty kalkulator4Dziedziczenie
Podstawy dziedziczeniaFunkcja super()Nadpisywanie metodDziedziczenie wielokrotneKolejność rozwiązywania metodPodsumowanie – hierarchia pracowników7Metody specjalne
Wprowadzenie do metod magicznychPrzeciążanie operatorówMagiczne metody kontenerówPowtórka – własna lista10Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec Strategy2Dekoratory
Wprowadzenie do dekoratorówDekorator propertyDekorator metody statycznejDekorator metody klasowej5Polimorfizm
Ponowne omówienie nadpisywania metodTypowanie kaczeKlasy i metody abstrakcyjneProjektowanie interfejsówPodsumowanie — kalkulator kształtów8Zaawansowane koncepcje programowania obiektowego
Kompozycja a dziedziczenieMiksyMetody statyczne i klasoweDekoratory klasMenedżery kontekstu11Wzorce projektowe, część 2
Wzorzec PolecenieWzorzec AdapterWzorzec DekoratorWzorzec Metoda szablonowaWzorzec StanWzorzec Kompozyt3Właściwości klas
Zmienne instancji a zmienne klasoweDekoratory właściwościAtrybuty prywatnePowtórzenie — menedżer kont bankowych6Hermetyzacja
Public, Protected, Private — pamięćModyfikatory dostępuUkrywanie informacjiDekoratory właściwości — zaawansowanePodsumowanie — system ewidencji uczniów12Projekt: System zarządzania biblioteką
Przegląd projektuKlasy Book i UserSystem wypożyczeńFunkcja wyszukiwaniaInterfejs administratoraTestowanie i integracjaPoćwicz samodzielnie: Kompilator Python online