Mixins
جزء من قسم البرمجة كائنية التوجه في رحلة Python على Coddy. الدرس 38 من 64.
المزيجات هي نوع خاص من الوراثة المتعددة يُستخدم «لإدخال» functionality إضافية إلى classes. وهي توفر methods محددة دون أن تكون classes مكتملة بحد ذاتها.
إليك مثالًا على mixin بسيط:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)لنُفصّل ما يفعله هذا الـmixin:
self.__dict__- تحتوي هذه الـattribute الخاصة على قاموس يضم جميعattributesللكائن وقيمهاjson.dumps()- تعمل هذه الـfunction على تحويل قاموس Python إلى سلسلة نصية بتنسيق JSON- يقوم الـmixin بدمج هذه الـJSON serialization functionality في أي class يرث منه
لنرَه الآن قيد التنفيذ:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailيضيف mixin وظائف JSON إلى أي class inherits منه:
user = User("Alice", "alice@example.com")
print(user.to_json())الناتج:
{"name": "Alice", "email": "alice@example.com"}الفكرة الأساسية: أصبح لدى الفئة User الآن to_json() method دون تعريفها مباشرةً. لقد أضاف الـ mixin تلك functionality!
أنشئ عدة mixins لوظائف مختلفة:
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__ للعمل مع attributes الخاصة بالكائن، بغض النظر عن class التي تستخدم mixin. هذه هي قوة mixins - فهي توفر functionality قابلة لإعادة الاستخدام وتعمل مع attributes الخاصة بأي class.
اجمع عدة 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الخصائص الرئيسية لـ mixins:
- ليست مخصصة لإنشاء كائنات منها بشكل مستقل
- توفر functionality محددة وقابلة لإعادة الاستخدام
- لا have عادةً أساليب
__init__ - غالبًا ما تنتهي أسماؤها بـ "Mixin" أو "able"
- يمكن دمجها مع inheritance المتعدد
- تعمل مع
self.__dict__أو ميزات الكائنات الشائعة الأخرى لتكون مرنة
نقطة أساسية: توفر mixins طريقة لمشاركة functionality عبر تسلسلات class different دون إنشاء أشجار inheritance معقدة. فهي تتيح لك «مزج» إمكانات محددة مثل التسلسل أو المقارنة أو الطباعة في أي class تحتاج إليها. وهذا يعزز إعادة استخدام الشيفرة ويحافظ على تركيز classes على مسؤولياتها الأساسية.
التحدي
متوسطفي هذا التحدي، ستنفّذ نظام تجارة إلكترونية بسيطًا باستخدام mixins وInheritance.
نفّذ classes المطلوبة في هذه الملفات (اتبع تعليقات TODO في كل ملف):
printablemixin.py- أنشئ PrintableMixin مع functionality للإخراج formatteddiscountmixin.py- نفّذ DiscountMixin لحسابات تخفيض السعرshippablemixin.py- أنشئ ShippableMixin لميزات الوزن وتكلفة الشحنproduct.py- طوّر Product الأساسي مع inheritance مناسب من mixinphysicalproduct.py- أنشئ PhysicalProduct الذي يمتد من Productdigitalproduct.py- نفّذ DigitalProduct مع behavior خاص بالخصم
جرّب بنفسك
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():
# اختبر الحالات الحدية مثل القيم الصفرية والسالبة
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():
# Test method overriding behavior
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():
# 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"
# يجب ألا تكون السمة 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()يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsمعامل selfMethodsAttributesدالة البناء (__init__)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةدالة ()superإعادة تعريف الدوال (Method Overriding)الوراثة المتعددةترتيب استدعاء الدوال (Method Resolution Order)مراجعة - هيكلية الموظفين5تعدد الأشكال
مراجعة إعادة تعريف الدوالمفهوم Duck Typingالأصناف والدوال المجردةتصميم الواجهاتملخص - حاسبة الأشكال8مفاهيم OOP المتقدمة
التركيب مقابل الوراثةMixinsدوال Static و Classمزخرفات Classمديرو السياق (Context Managers)تدرّب بنفسك: مترجم Python عبر الإنترنت