Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط المزين

جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 96 من 110.

يتيح لك نمط المزين (Decorator pattern) إضافة سلوكيات جديدة إلى الكائنات ديناميكيًا عن طريق تغليفها في كائنات خاصة تسمى "المزينات" (decorators). وعلى عكس الوراثة (inheritance)، التي تضيف السلوك في وقت التجميع (compile time)، تقوم المزينات بإرفاق مسؤوليات إضافية في وقت التشغيل (runtime) دون تعديل الفئة الأصلية (original class).

الفكرة الأساسية هي أن كلاً من الكائن الأصلي والمزخرفات (decorators) ينفذان نفس الواجهة (interface).

يقوم المزخرف (decorator) بتغليف الكائن الأصلي، ويفوض المهام إليه، ويضيف سلوكه الخاص قبل ذلك أو بعده:

abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

class SugarDecorator implements Coffee {
  final Coffee _coffee;
  SugarDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Sugar';

  @override
  double getCost() => _coffee.getCost() + 0.2;
}

void main() {
  Coffee coffee = SimpleCoffee();
  coffee = MilkDecorator(coffee);
  coffee = SugarDecorator(coffee);

  print(coffee.getDescription());  // قهوة بسيطة، حليب، سكر
  print(coffee.getCost());         // 2.7
}

يقوم كل مزخرف (decorator) بتغليف الكائن السابق وتحسينه. يمكنك تكديس عدة مزخرفات بأي مجموعة، مما يؤدي إلى إنشاء سلوك مرن دون الحاجة إلى إنشاء فئة فرعية (subclass) لكل مجموعة محتملة. هذا النمط مثالي عندما تحتاج إلى إضافة ميزات إلى الكائنات دون التأثير على الكائنات الأخرى من نفس الفئة.

challenge icon

التحدي

سهل

لنقم ببناء نظام لطلب البيتزا باستخدام نمط المزين (Decorator pattern)! ستقوم بإنشاء بيتزا أساسية يمكن تحسينها بإضافات متنوعة، حيث تغلف كل إضافة العنصر السابق وتضيف إلى كل من الوصف والسعر.

ستقوم بتنظيم الكود الخاص بك في ملفين:

  • pizza.dart: يحتوي هذا الملف على واجهة البيتزا وجميع فئات المزين (decorator classes). ابدأ بفئة مجردة Pizza تحدد طريقتين: String getDescription() و double getPrice(). ثم أنشئ فئة PlainPizza التي تنفذ Pizza - حيث تعيد Plain Pizza كوصف لها وتكلفتها 8.0. الآن قم ببناء ثلاث فئات مزينة يقوم كل منها بتغليف Pizza وتحسينها:
    • CheeseDecorator - يضيف , Cheese إلى الوصف و 1.5 إلى السعر
    • PepperoniDecorator - يضيف , Pepperoni إلى الوصف و 2.0 إلى السعر
    • MushroomDecorator - يضيف , Mushrooms إلى الوصف و 1.25 إلى السعر
    يجب أن تنفذ كل فئة مزينة Pizza، وتقبل Pizza في منشئها (constructor)، وتفوض العمل إلى البيتزا المغلفة قبل إضافة مساهمتها الخاصة.
  • main.dart: استورد ملف البيتزا الخاص بك ووضح كيف تتراكم المزينّات لبناء طلب مخصص. ابدأ بـ PlainPizza، ثم غلفها بـ CheeseDecorator، ثم غلف ذلك بـ PepperoniDecorator، وأخيرًا غلف كل شيء بـ MushroomDecorator. اطبع وصف البيتزا النهائي، ثم اطبع Total: $ متبوعًا بالسعر.

لاحظ كيف يبني كل مزين على المزين السابق - أنت تقوم بإنشاء بيتزا كاملة الإضافات عن طريق تكديس مزينات بسيطة وذات مسؤولية واحدة!

المخرجات المتوقعة:

Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75

ورقة مرجعية

يضيف نمط المزين (Decorator pattern) سلوكيات جديدة إلى الكائنات ديناميكيًا عن طريق تغليفها في كائنات مزينة. يقوم كل من الكائن الأصلي والمزينات بتنفيذ الواجهة البرمجية (interface) نفسها.

المكونات الرئيسية:

  • فئة مجردة (abstract class) أو واجهة (interface) تحدد الطرق (methods) المشتركة
  • فئة ملموسة (concrete class) تنفذ الوظائف الأساسية
  • فئات مزينة (decorator classes) تغلف الكائن الأصلي وتعززه

كل مزين (decorator):

  • ينفذ نفس الواجهة التي ينفذها الكائن الذي يزينه
  • يحتفظ بمرجع للكائن المغلف
  • يفوض الاستدعاءات إلى الكائن المغلف
  • يضيف سلوكه الخاص قبل أو بعد التفويض
abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

// Stack multiple decorators
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);

يمكن تكديس المزينات بأي مزيج، مما يخلق سلوكًا مرنًا دون الحاجة إلى إنشاء فئات فرعية (subclasses) لكل مزيج محتمل.

جرّب بنفسك

import 'pizza.dart';

void main() {
  // TODO: PlainPizza قم بإنشاء
  
  // TODO: CheeseDecorator قم بتغليفه بـ
  
  // TODO: PepperoniDecorator قم بتغليف ذلك بـ
  
  // TODO: MushroomDecorator قم بتغليف كل شيء بـ
  
  // TODO: قم بطباعة وصف البيتزا النهائي
  
  // TODO: متبوعاً بالسعر "Total: $" قم بطباعة
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming