Menu
Coddy logo textTech

Mixins

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Python של Coddy. שיעור 38 מתוך 64.

Mixins הם סוג מיוחד של ירושה מרובה שמשמשת כדי "לערבב פנימה" פונקציונליות נוספת למחלקות. הם מספקים מתודות ספציפיות בלי להיות מחלקות שלמות בפני עצמם.

הנה דוגמה למיקסין פשוט:

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

נפרט מה ה־mixin הזה עושה:

  • self.__dict__ - התכונה המיוחדת הזאת מכילה מילון של כל התכונות של האובייקט והערכים שלהן
  • json.dumps() - הפונקציה הזאת ממירה את מילון Python למחרוזת בפורמט JSON
  • ה־mixin "מוסיף" את פונקציונליות הסריאליזציה של JSON לכל מחלקה שיורשת ממנו

עכשיו נראה את זה בפעולה:

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

ה־mixin מוסיף פונקציונליות של JSON לכל מחלקה שיורשת ממנו:

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

פלט:

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

התובנה המרכזית: למחלקה User יש כעת את המתודה to_json() מבלי להגדיר אותה ישירות. ה־mixin "ערבב פנימה" את הפונקציונליות הזאת!

צור כמה מיקסינים לפונקציונליות שונה:

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__

כל mixin ניגש אל self.__dict__ כדי לעבוד עם התכונות של האובייקט, בלי קשר למחלקה שמשתמשת ב-mixin. זהו הכוח של mixins — הם מספקים פונקציונליות לשימוש חוזר שעובדת עם התכונות של כל מחלקה.

שלבו כמה mixins במחלקה אחת:

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

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

השתמשו בכל הפונקציונליות של ה-mixin:

print(product1.to_json())         # מתוך JSONSerializableMixin
product1.pretty_print()          # מתוך PrintableMixin
print(product1 == product2)      # מתוך ComparableMixin

פלט:

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

מאפיינים מרכזיים של מיקסינים:

  • לא נועדו ליצירה כמופעים בפני עצמם
  • מספקים פונקציונליות מסוימת שניתן לעשות בה שימוש חוזר
  • בדרך כלל אין להם מתודות __init__
  • שמותיהם מסתיימים לעיתים קרובות ב-"Mixin" או ב-"able"
  • ניתן לשלב אותם עם ירושה מרובה
  • פועלים עם self.__dict__ או עם תכונות נפוצות אחרות של אובייקטים כדי להיות גמישים

נקודה חשובה: מיקסינים מאפשרים לשתף פונקציונליות בין היררכיות מחלקות שונות בלי ליצור עצי ירושה מורכבים. הם מאפשרים לך להוסיף למחלקה כלשהי שזקוקה לכך יכולות ספציפיות, כמו סריאליזציה, השוואה או הדפסה. כך מתאפשר שימוש חוזר בקוד, והמחלקות נשארות ממוקדות בתחומי האחריות העיקריים שלהן.

challenge icon

אתגר

בינוני

באתגר הזה תממשו מערכת מסחר אלקטרוני פשוטה באמצעות מיקסינים וירושה.

ממשו את המחלקות הנדרשות בקבצים האלה (פעלו לפי הערות TODO בכל קובץ):

  • printablemixin.py - צרו את PrintableMixin עם פונקציונליות להצגת פלט מעוצב
  • discountmixin.py - ממשו את DiscountMixin לחישוב הפחתת מחירים
  • shippablemixin.py - בנו את ShippableMixin לתמיכה במשקל ובעלויות משלוח
  • product.py - פתחו את מחלקת הבסיס Product עם ירושה מתאימה ממיקסינים
  • physicalproduct.py - צרו את המחלקה PhysicalProduct שיורשת מ-Product
  • digitalproduct.py - ממשו את המחלקה DigitalProduct עם התנהגות מיוחדת להנחות
REQUIRED OUTPUT FORMAT: [Your translated content here]

נסו בעצמכם

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():
    # בדקו את הפונקציונליות הבסיסית של כל המחלקות
    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():
    # בדקו מקרי קצה כמו אפס וערכים שליליים
    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():
    # בדקו עם ערכים גדולים מאוד
    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():
    # בדקו את קשרי הירושה
    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():
    # בדקו את ההתנהגות של דריסת מתודות
    p = Product("Regular Product", 100)
    digital = DigitalProduct("Digital Product", 100)
    
    # אותו מחיר, אותו אחוז הנחה, תוצאות שונות
    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)}"
    
    # מוצר דיגיטלי צריך תמיד להחיל הנחה של 10% ללא תלות בפרמטר
    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():
    # בדקו התנהגות פולימורפית עם רשימה של סוגי מוצרים שונים
    products = [
        Product("Regular", 100),
        PhysicalProduct("Physical", 100),
        DigitalProduct("Digital", 100)
    ]
    
    # הגדירו את המשקל של המוצר הפיזי
    products[1].set_weight(10)
    
    # התוצאות הצפויות עבור apply_discount(20)
    expected_discounts = [80, 80, 90]
    
    for i, product in enumerate(products):
        # לכולם צריכה להיות המתודה print_details
        assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
        
        # לכולם צריכה להיות המתודה apply_discount, אך עם מימושים שונים
        assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
    print("Polymorphism test passed!")

def test_attribute_access():
    # בדיקת דפוסי גישה למאפיינים
    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"
    
    # המאפיין weight לא אמור להתקיים לפני הקריאה ל-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!")

# הרכיב הראשי להרצת הבדיקות
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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Python אונליין