Mixins
جزء من قسم Object Oriented Programming في رحلة Python على Coddy — الدرس 38 من 64.
تُعد الميكسينز (Mixins) نوعاً خاصاً من الوراثة المتعددة تُستخدم لـ "دمج" وظائف إضافية في الأصناف (classes). فهي توفر دوالاً (methods) محددة دون أن تكون أصنافاً كاملة بحد ذاتها.
إليك مثال على mixin بسيط:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)دعونا نحلل ما تفعله هذه الـ mixin:
self.__dict__- تحتوي هذه السمة الخاصة على قاموس لجميع سمات الكائن وقيمهاjson.dumps()- تقوم هذه الدالة بتحويل قاموس Python إلى سلسلة نصية بتنسيق JSON- تقوم الـ mixin بـ "دمج" وظيفة تسلسل JSON هذه في أي فئة (class) ترث منها
الآن دعونا نرى ذلك عملياً:
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 "بدمج" تلك الوظيفة!
قم بإنشاء 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__ للعمل مع سمات الكائن، بغض النظر عن الفئة التي تستخدم الـ 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الخصائص الرئيسية للـ mixins:
- ليست مخصصة ليتم إنشاؤها بشكل مستقل
- توفر وظائف محددة وقابلة لإعادة الاستخدام
- عادةً لا تحتوي على دوال
__init__ - غالبًا ما تنتهي أسماؤها بـ "Mixin" أو "able"
- يمكن دمجها مع الوراثة المتعددة
- تعمل مع
self.__dict__أو ميزات الكائنات الشائعة الأخرى لتكون مرنة
نقطة رئيسية: توفر الـ Mixins طريقة لمشاركة الوظائف عبر هياكل فئات (class hierarchies) مختلفة دون إنشاء أشجار وراثة معقدة. فهي تتيح لك "دمج" قدرات محددة مثل التسلسل (serialization)، أو المقارنة، أو الطباعة في أي فئة تحتاج إليها. يعزز هذا من إعادة استخدام الكود ويحافظ على تركيز الفئات على مسؤولياتها الأساسية.
التحدي
متوسطفي هذا التحدي، ستقوم بتنفيذ نظام تجارة إلكترونية بسيط باستخدام الـ mixins والوراثة.
قم بتنفيذ الفئات المطلوبة في هذه الملفات (اتبع تعليقات TODO في كل ملف):
printablemixin.py- أنشئPrintableMixinمع وظيفة الإخراج المنسقdiscountmixin.py- قم بتنفيذDiscountMixinلحسابات تخفيض السعرshippablemixin.py- قم ببناءShippableMixinلميزات الوزن وتكلفة الشحنproduct.py- قم بتطوير فئةProductالأساسية مع وراثة الـ mixin المناسبةphysicalproduct.py- أنشئ فئةPhysicalProductالتي ترث منProductdigitalproduct.py- قم بتنفيذ فئةDigitalProductمع سلوك خصم خاص
ورقة مرجعية
الـ Mixins هي نوع خاص من الوراثة المتعددة تُستخدم لـ "دمج" وظائف إضافية في الفئات (classes) دون أن تكون فئات كاملة بحد ذاتها.
مثال أساسي على الـ mixin:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailيمكن دمج عدة 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__
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = priceالخصائص الرئيسية للـ mixins:
- غير مخصصة ليتم إنشاؤها (instantiated) بشكل مستقل
- توفر وظائف محددة وقابلة لإعادة الاستخدام
- عادةً لا تحتوي على دوال
__init__ - غالباً ما تنتهي أسماؤها بـ "Mixin" أو "able"
- يمكن دمجها مع الوراثة المتعددة
جرّب بنفسك
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():
# اختبار سلوك تجاوز الدوال (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():
# اختبار سلوك تعدد الأشكال (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"
# يجب ألا تكون خاصية الوزن موجودة قبل استدعاء 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()يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsمعامل selfMethodsAttributesدالة البناء (__init__)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةدالة ()superإعادة تعريف الدوال (Method Overriding)الوراثة المتعددةترتيب استدعاء الدوال (Method Resolution Order)مراجعة - هيكلية الموظفين5تعدد الأشكال
مراجعة إعادة تعريف الدوالمفهوم Duck Typingالأصناف والدوال المجردةتصميم الواجهاتملخص - حاسبة الأشكال8مفاهيم OOP المتقدمة
التركيب مقابل الوراثةMixinsدوال Static و Classمزخرفات Classمديرو السياق (Context Managers)