Menu
Coddy logo textTech

Mixins

Teil des Abschnitts Objektorientierte Programmierung der Python-Journey von Coddy. Lektion 38 von 64.

Mixins sind eine besondere Form der Mehrfachvererbung, die dazu dient, zusätzliche Funktionalität in Klassen „einzumischen“. Sie stellen spezifische Methoden bereit, ohne selbst vollständige Klassen zu sein.

Hier ist ein Beispiel für ein einfaches Mixin:

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

Sehen wir uns an, was dieses Mixin macht:

  • self.__dict__ – Dieses spezielle Attribut enthält ein Wörterbuch mit allen Attributen des Objekts und ihren Werten
  • json.dumps() – Diese Funktion wandelt das Python-Wörterbuch in eine JSON-formatierte Zeichenkette um
  • Das Mixin „mischt“ diese JSON-Serialisierungsfunktionalität in jede Klasse ein, die von ihm erbt

Sehen wir es uns jetzt in Aktion an:

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

Das Mixin fügt jeder Klasse, die davon erbt, JSON-Funktionalität hinzu:

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

Ausgabe:

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

Die zentrale Erkenntnis: Die Klasse User verfügt jetzt über die Methode to_json(), ohne sie direkt zu definieren. Das Mixin hat diese Funktionalität „eingemischt“!

Erstelle mehrere Mixins für unterschiedliche Funktionalität:

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__

Jedes Mixin greift auf self.__dict__ zu, um mit den Attributen des Objekts zu arbeiten, unabhängig davon, welche Klasse das Mixin verwendet. Das ist die Stärke von Mixins – sie stellen wiederverwendbare Funktionalität bereit, die mit den Attributen jeder Klasse funktioniert.

Mehrere Mixins in einer Klasse kombinieren:

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

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

Verwende alle Funktionen der Mixins:

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

Ausgabe:

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

Wichtige Merkmale von Mixins:

  • Nicht dazu gedacht, eigenständig instanziiert zu werden
  • Bieten spezifische, wiederverwendbare Funktionalität
  • Haben normalerweise keine __init__-Methoden
  • Namen enden häufig mit „Mixin“ oder „able“
  • Können mit Mehrfachvererbung kombiniert werden
  • Arbeiten mit self.__dict__ oder anderen gängigen Objekteigenschaften, um flexibel zu sein

Wichtiger Punkt: Mixins bieten eine Möglichkeit, Funktionalität über verschiedene Klassenhierarchien hinweg gemeinsam zu nutzen, ohne komplexe Vererbungsbäume zu erstellen. Sie ermöglichen es dir, bestimmte Fähigkeiten wie Serialisierung, Vergleich oder Ausgabe in jede Klasse „einzumischen“, die diese benötigt. Dies fördert die Wiederverwendung von Code und sorgt dafür, dass Klassen auf ihre Hauptaufgaben fokussiert bleiben.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementierst du ein einfaches E-Commerce-System mithilfe von Mixins und Vererbung.

Implementiere die erforderlichen Klassen in diesen Dateien (befolge die TODO-Kommentare in jeder Datei):

  • printablemixin.py – Erstelle das PrintableMixin mit Funktionen für formatierte Ausgaben
  • discountmixin.py – Implementiere das DiscountMixin für die Berechnung von Preisnachlässen
  • shippablemixin.py – Erstelle das ShippableMixin für Funktionen zu Gewicht und Versandkosten
  • product.py – Entwickle die Basisklasse Product mit geeigneter Mixin-Vererbung
  • physicalproduct.py – Erstelle die Klasse PhysicalProduct, die Product erweitert
  • digitalproduct.py – Implementiere die Klasse DigitalProduct mit besonderem Rabattverhalten

Probier es selbst

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():
    # Test basic functionality of all classes
    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():
    # Teste Grenzfälle wie Null- und negative Werte
    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():
    # Teste mit sehr großen Werten
    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():
    # Teste Vererbungsbeziehungen
    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():
    # Test method overriding behavior
    p = Product("Regular Product", 100)
    digital = DigitalProduct("Digital Product", 100)
    
    # Gleicher Preis, gleicher Rabattprozentsatz, unterschiedliche Ergebnisse
    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)}"
    
    # Digitales Produkt sollte immer 10% Rabatt anwenden, unabhängig vom 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():
    # Teste polymorphes Verhalten mit einer Liste verschiedener Produkttypen
    products = [
        Product("Regular", 100),
        PhysicalProduct("Physical", 100),
        DigitalProduct("Digital", 100)
    ]
    
    # Gewicht für das physische Produkt setzen
    products[1].set_weight(10)
    
    # Erwartete Ergebnisse für apply_discount(20)
    expected_discounts = [80, 80, 90]
    
    for i, product in enumerate(products):
        # Alle sollten die Methode print_details haben
        assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
        
        # Alle sollten die Methode apply_discount haben, aber mit unterschiedlichen Implementierungen
        assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
    print("Polymorphism test passed!")

def test_attribute_access():
    # Test attribute access patterns
    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"
    
    # Das weight-Attribut sollte nicht existieren, bevor set_weight aufgerufen wird
    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!")

# Haupt-Test-Runner
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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Python-Compiler