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

نمط الأمر

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

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

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

#include <iostream>
#include <memory>
#include <vector>

// المستقبل (Receiver) - يعرف كيفية تنفيذ العمليات
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// واجهة الأمر (Command interface)
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// الأوامر الملموسة (Concrete Commands)
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

class LightOffCommand : public Command {
    Light& light;
public:
    LightOffCommand(Light& l) : light(l) {}
    void execute() override { light.turnOff(); }
    void undo() override { light.turnOn(); }
};

// المستدعي (Invoker)
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

يقوم كل أمر بتخزين مرجع للمستقبل (receiver) الخاص به ويستدعي الطريقة المناسبة عند تنفيذه. لا يعرف المستدعي (invoker) ما هو الإجراء الذي سيحدث - فهو يكتفي باستدعاء execute(). من خلال تخزين الأوامر في سجل التاريخ، يمكنك بسهولة تنفيذ التراجع عن طريق استدعاء طريقة undo() الخاصة بكل أمر.

استخدم نمط Command عندما تحتاج إلى وضع العمليات في طابور، أو تنفيذ التراجع/الإعادة، أو فصل مرسل الطلب عن معالجه.

challenge icon

التحدي

سهل

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

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

  • TextDocument.h: قم بإنشاء فئة المستقبل (receiver) التي تحتفظ بمحتوى النص الفعلي وتتلاعب به.

    يجب أن تقوم فئة TextDocument بتخزين محتوى المستند كسلسلة نصية (string). قم بتنفيذ هذه الدوال:

    • insertText(const std::string& text) — يضيف النص إلى نهاية المحتوى.
    • deleteText(int count) — يزيل آخر count من الأحرف من المحتوى (لا تفعل شيئاً إذا تجاوز count طول المحتوى).
    • getContent() — يعيد المحتوى الحالي كمرجع ثابت (const reference).
  • Command.h: قم بتعريف واجهة الأمر (command interface) وفئات الأوامر الملموسة (concrete command classes).

    قم بإنشاء فئة Command مجردة مع دوال افتراضية بحتة (pure virtual methods) وهي execute() و undo()، بالإضافة إلى مدمر افتراضي (virtual destructor).

    ثم قم بتنفيذ أمرين ملموسين:

    • InsertCommand — يأخذ مرجعاً لـ TextDocument وسلسلة نصية لإدراجها. عند التنفيذ، يقوم بإدراج النص. وعند التراجع، يقوم بحذف نفس عدد الأحرف التي تم إدراجها.
    • DeleteCommand — يأخذ مرجعاً لـ TextDocument وعدد الأحرف المراد حذفها. يحتاج إلى تخزين النص المحذوف حتى يتمكن من استعادته عند التراجع. عند التنفيذ، يقوم بإزالة الأحرف من النهاية. وعند التراجع، يقوم بإدراج النص المخزن مرة أخرى.
  • TextEditor.h: قم بإنشاء المستدعي (invoker) الذي يدير تنفيذ الأوامر والسجل (history).

    يجب أن تحمل فئة TextEditor مرجعاً لـ TextDocument وتحتفظ بسجل للأوامر المنفذة باستخدام vector من unique_ptr<Command>. قم بتنفيذ:

    • executeCommand(std::unique_ptr<Command> cmd) — ينفذ الأمر ويضيفه إلى السجل.
    • undo() — يتراجع عن أحدث أمر ويزيله من السجل (لا تفعل شيئاً إذا كان السجل فارغاً).
    • showContent() — يطبع محتوى المستند الحالي، أو [empty] إذا كان المحتوى فارغاً.
  • main.cpp: عرض نمط الأمر مع خاصية التراجع.

    اقرأ ثلاثة مدخلات:

    1. النص الأول المراد إدراجه (string)
    2. النص الثاني المراد إدراجه (string)
    3. عدد الأحرف المراد حذفها (integer)

    قم بإنشاء TextDocument و TextEditor. ثم قم بإجراء هذه العمليات بالترتيب، مع عرض المحتوى بعد كل خطوة:

    1. إدراج النص الأول، ثم عرض المحتوى.
    2. إدراج النص الثاني، ثم عرض المحتوى.
    3. حذف عدد الأحرف المحدد، ثم عرض المحتوى.
    4. التراجع مرة واحدة، ثم عرض المحتوى.
    5. التراجع مرة أخرى، ثم عرض المحتوى.

على سبيل المثال، مع المدخلات Hello و World و 3:

Hello
Hello World
Hello Wo
Hello World
Hello

مع المدخلات Code و Editor و 6:

Code
CodeEditor
Code
CodeEditor
Code

لاحظ كيف يعرف كل أمر كيفية عكس نفسه. يتذكر DeleteCommand ما قام بحذفه حتى يتمكن من استعادته، ويعرف InsertCommand بالضبط عدد الأحرف التي يجب إزالتها عند التراجع. هذه هي قوة نمط الأمر — تصبح العمليات كائنات من الدرجة الأولى يمكن تخزينها وتنفيذها وعكسها.

ورقة مرجعية

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

يتكون النمط من أربعة مكونات رئيسية:

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

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

// Receiver - يعرف كيفية تنفيذ العمليات
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Command interface - واجهة الأمر
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Concrete Command - أمر ملموس
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

// Invoker - المستدعي
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

يخزن كل أمر مرجعاً للمستقبل الخاص به ويستدعي الطريقة المناسبة عند تنفيذه. لا يعرف المستدعي الإجراء الذي سيحدث، بل يكتفي باستدعاء execute(). من خلال تخزين الأوامر في سجل (history)، يمكنك تنفيذ خاصية التراجع عن طريق استدعاء طريقة undo() لكل أمر.

استخدم نمط الأمر عندما تحتاج إلى جدولة العمليات، أو تنفيذ التراجع/الإعادة (undo/redo)، أو فصل مرسل الطلب عن معالجه.

جرّب بنفسك

#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"

int main() {
    // قراءة المدخلات
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: إنشاء TextDocument و TextEditor
    
    // TODO: تنفيذ العمليات بالترتيب، مع عرض المحتوى بعد كل عملية:
    // 1. إدراج النص الأول، ثم عرض المحتوى
    // 2. إدراج النص الثاني، ثم عرض المحتوى
    // 3. حذف عدد الأحرف المحدد، ثم عرض المحتوى
    // 4. التراجع مرة واحدة، ثم عرض المحتوى
    // 5. التراجع مرة أخرى، ثم عرض المحتوى
    
    // تلميح: استخدم std::make_unique<InsertCommand>(...) و 
    //       std::make_unique<DeleteCommand>(...) لإنشاء الأوامر

    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming