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

نمط الأمر

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

يقوم نمط الأمر (Command pattern) بتغليف الطلب ككائن، مما يتيح لك تخصيص العملاء بطلبات مختلفة، أو وضع العمليات في طابور، أو دعم خاصية التراجع. فبدلاً من استدعاء دالة (method) بشكل مباشر، تقوم بتغليف الإجراء في كائن أمر يمكن تخزينه، أو تمريره، أو تنفيذه لاحقاً.

يتضمن هذا النمط ثلاثة مكونات رئيسية: واجهة Command (الأمر) مع طريقة execute، و Concrete Commands (الأوامر الملموسة) التي تنفذ إجراءات محددة، و Receiver (المستقبل) الذي يقوم بالعمل الفعلي. كما يوجد Invoker (المستدعي) اختياري يقوم بتشغيل الأوامر دون معرفة ما تفعله:

abstract class Command {
  void execute();
}

class Light {
  void turnOn() => print('Light is ON');
  void turnOff() => print('Light is OFF');
}

class TurnOnCommand implements Command {
  final Light light;
  TurnOnCommand(this.light);

  @override
  void execute() => light.turnOn();
}

class TurnOffCommand implements Command {
  final Light light;
  TurnOffCommand(this.light);

  @override
  void execute() => light.turnOff();
}

class RemoteControl {
  Command? _command;

  void setCommand(Command command) {
    _command = command;
  }

  void pressButton() {
    _command?.execute();
  }
}

void main() {
  var light = Light();
  var remote = RemoteControl();

  remote.setCommand(TurnOnCommand(light));
  remote.pressButton();  // Light is ON

  remote.setCommand(TurnOffCommand(light));
  remote.pressButton();  // Light is OFF
}

لا يعرف RemoteControl أي شيء عن الأضواء - فهو ينفذ فقط أي أمر يُعطى له. هذا الفصل يجعل من السهل إضافة أوامر جديدة دون تعديل المستدعي (invoker). نمط الأمر (Command pattern) مثالي لتنفيذ أنظمة التراجع/الإعادة، أو قوائم انتظار المهام، أو تسجيل الماكرو حيث تحتاج إلى التعامل مع الإجراءات ككائنات من الدرجة الأولى.

challenge icon

التحدي

سهل

لنقم ببناء نظام أوامر لمحرر نصوص باستخدام نمط الأمر (Command pattern)! ستقوم بإنشاء نظام يغلف عمليات النص ككائنات أوامر، مما يسمح للمحرر بتنفيذ إجراءات مختلفة دون معرفة تفاصيل كل عملية. هذه حالة استخدام كلاسيكية لنمط الأمر - فكر فيها كنظام تراجع/إعادة (undo/redo) حيث يتم تخزين كل إجراء ككائن.

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

  • commands.dart: أنشئ نظام الأوامر الخاص بك هنا. ابدأ بفئة مجردة (abstract class) باسم Command تعلن عن تابع execute(). ثم أنشئ فئة TextDocument (المستقبل - receiver) التي تحمل حقل String content (فارغ في البداية) ولديها ثلاثة توابع:
    • append(String text) - يضيف النص إلى نهاية المحتوى
    • clear() - يضبط المحتوى إلى سلسلة فارغة
    • show() - يطبع Document: [content]
    الآن قم بتنفيذ ثلاثة أوامر ملموسة (concrete commands) تعمل مع TextDocument:
    • AppendCommand - يأخذ مستنداً ونصاً في منشئه (constructor)، ويقوم بإلحاق النص عند التنفيذ
    • ClearCommand - يأخذ مستنداً في منشئه، ويقوم بمسحه عند التنفيذ
    • ShowCommand - يأخذ مستنداً في منشئه، ويعرض محتواه عند التنفيذ
    أخيراً، أنشئ فئة TextEditor (المستدعي - invoker) التي تخزن قائمة من الأوامر. يجب أن تحتوي على تابع addCommand(Command command) لإضافة الأوامر إلى الطابور، وتابع runAll() الذي ينفذ جميع الأوامر الموجودة في الطابور بالترتيب.
  • main.dart: استورد ملف الأوامر الخاص بك واستعرض نمط الأمر. أنشئ TextDocument و TextEditor. قم بوضع الأوامر التالية في الطابور بالترتيب: إلحاق Hello، إلحاق World، عرض المستند، إلحاق !، العرض مرة أخرى، مسح المستند، والعرض لمرة أخيرة. ثم قم بتشغيل جميع الأوامر الموجودة في الطابور.

لاحظ كيف أن TextEditor لا يعرف أي شيء عن الإلحاق أو المسح أو العرض - فهو ينفذ فقط أي أوامر تُعطى له. هذا الفصل (decoupling) هو جوهر نمط الأمر!

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

Document: Hello World
Document: Hello World!
Document: 

ورقة مرجعية

يقوم نمط الأمر (Command pattern) بتغليف الطلب ككائن، مما يسمح لك بتمرير طلبات مختلفة كمعاملات للعملاء، أو جدولة العمليات، أو دعم خاصية التراجع.

يتضمن هذا النمط ثلاثة مكونات رئيسية:

  • واجهة Command مع طريقة execute
  • الأوامر الملموسة (Concrete Commands) التي تنفذ إجراءات محددة
  • المستلم (Receiver) الذي يقوم بالعمل الفعلي
  • المستدعي (Invoker) (اختياري) الذي يطلق الأوامر دون معرفة ما تفعله

الهيكل الأساسي:

abstract class Command {
  void execute();
}

class Receiver {
  void action() => print('Action performed');
}

class ConcreteCommand implements Command {
  final Receiver receiver;
  ConcreteCommand(this.receiver);

  @override
  void execute() => receiver.action();
}

class Invoker {
  Command? _command;

  void setCommand(Command command) {
    _command = command;
  }

  void trigger() {
    _command?.execute();
  }
}

مثال مع نظام تحكم في الإضاءة:

class Light {
  void turnOn() => print('Light is ON');
  void turnOff() => print('Light is OFF');
}

class TurnOnCommand implements Command {
  final Light light;
  TurnOnCommand(this.light);

  @override
  void execute() => light.turnOn();
}

class RemoteControl {
  Command? _command;

  void setCommand(Command command) {
    _command = command;
  }

  void pressButton() {
    _command?.execute();
  }
}

void main() {
  var light = Light();
  var remote = RemoteControl();

  remote.setCommand(TurnOnCommand(light));
  remote.pressButton();  // Light is ON
}

لا يعرف المستدعي (invoker) أي شيء عن المستلم (receiver) - فهو ينفذ فقط أي أمر يُعطى له. هذا الفصل (decoupling) يجعل من السهل إضافة أوامر جديدة دون تعديل المستدعي.

جرّب بنفسك

import 'commands.dart';

void main() {
  // TODO: إنشاء مثيل لـ TextDocument

  // TODO: إنشاء مثيل لـ TextEditor

  // TODO: وضع الأوامر التالية في قائمة الانتظار بالترتيب:
  // 1. إلحاق "Hello"
  // 2. إلحاق " World"
  // 3. عرض المستند
  // 4. إلحاق "!"
  // 5. عرض المستند مرة أخرى
  // 6. مسح المستند
  // 7. عرض المستند للمرة الأخيرة

  // TODO: تشغيل جميع الأوامر الموجودة في قائمة الانتظار
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming