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

نمط الأمر

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

إن نمط الأمر (Command pattern) هو نمط سلوكي يحول الطلب إلى كائن مستقل يحتوي على جميع المعلومات المتعلقة بالطلب. يتيح لك هذا التحويل تمرير الطلبات كوسيطات للدوال (method arguments)، أو وضعها في طابور، أو تسجيلها، أو دعم العمليات القابلة للتراجع.

يفصل هذا النمط الكائن الذي يستدعي العملية عن الكائن الذي يعرف كيفية تنفيذها. ويتكون من أربعة مكونات رئيسية: واجهة الأوامر، والأوامر الملموسة، والمستقبل (الكائن الذي يقوم بالعمل الفعلي)، والمستدعي (الكائن الذي يطلق الأوامر).

public interface ICommand
{
    void Execute();
}

public class Light  // المستقبل
{
    public void TurnOn() => Console.WriteLine("Light is ON");
    public void TurnOff() => Console.WriteLine("Light is OFF");
}

public class TurnOnCommand : ICommand
{
    private Light _light;
    
    public TurnOnCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOn();
}

public class TurnOffCommand : ICommand
{
    private Light _light;
    
    public TurnOffCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOff();
}

يقوم المستدعي (invoker) بتخزين الأوامر وتنفيذها دون معرفة ما تفعله:

public class RemoteControl  // المستدعي (Invoker)
{
    private ICommand _command;
    
    public void SetCommand(ICommand command) => _command = command;
    
    public void PressButton() => _command.Execute();
}

// الاستخدام
var light = new Light();
var remote = new RemoteControl();

remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();  // الضوء يعمل (Light is ON)

remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();  // الضوء مطفأ (Light is OFF)

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

challenge icon

التحدي

سهل

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

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

  • Command.cs: قم بتعريف واجهة ICommand في مساحة الأسماء TextEditor مع طريقة واحدة Execute(). ثم قم بإنشاء فئتين للأوامر الملموسة:
    • InsertCommand - تأخذ Document وسلسلة نصية text في منشئها (constructor). عند تنفيذها، تقوم بإلحاق النص بالمستند وطباعة Inserted: {text}
    • DeleteCommand - تأخذ Document و int count في منشئها. عند تنفيذها، تقوم بإزالة آخر count من الأحرف من المستند وطباعة Deleted: {count} characters
  • Document.cs: قم بإنشاء فئة Document في نفس مساحة الأسماء. هذا هو المستقبل (receiver) الخاص بك - الكائن الذي يقوم فعلياً بعمليات النص. يجب أن يحتوي على:
    • حقل نصي خاص (private string field) لتخزين المحتوى (يتم تهيئته كسلسلة فارغة)
    • طريقة Append(string text) تضيف النص إلى المحتوى
    • طريقة RemoveLast(int count) تزيل آخر count من الأحرف (إذا تجاوز count طول المحتوى، قم بمسح كل المحتوى)
    • طريقة GetContent() تعيد المحتوى الحالي
  • Program.cs: قم بإنشاء فئة Editor تعمل كالمستدعي (invoker). يجب أن تحتوي على طريقة SetCommand(ICommand command) وطريقة ExecuteCommand() التي تشغل الأمر الحالي. في الكود الرئيسي الخاص بك، وضح نمط الأمر من خلال معالجة سلسلة من عمليات التحرير.

ستتلقى المدخلات التالية:

  • عدد الأوامر المراد تنفيذها
  • لكل أمر: نوع الأمر (insert أو delete) متبوعاً إما بالنص المراد إدراجه أو عدد الأحرف المراد حذفها

بعد تنفيذ جميع الأوامر، اطبع محتوى المستند النهائي في سطر جديد بالتنسيق Content: {content}.

على سبيل المثال، إذا كانت المدخلات هي:

4
insert
Hello
insert
 World
delete
3
insert
!

يجب أن يكون الناتج:

Inserted: Hello
Inserted:  World
Deleted: 3 characters
Inserted: !
Content: Hello Wo!

لاحظ كيف أن Editor (المستدعي) لا يعرف شيئاً عن معالجة النصوص - فهو ببساطة ينفذ أي أمر يُعطى له. تغلف الأوامر كلاً من الإجراء والمستقبل، مما يجعل من السهل إضافة عمليات جديدة مثل ReplaceCommand دون تغيير المحرر!

ورقة مرجعية

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

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

  • واجهة الأمر (Command interface) - تحدد طريقة Execute()
  • الأوامر الملموسة (Concrete commands) - تنفذ واجهة الأمر وتغلف إجراءات محددة
  • المستقبل (Receiver) - الكائن الذي يقوم بالعمل الفعلي
  • المستدعي (Invoker) - الكائن الذي يطلق الأوامر دون معرفة كيفية تنفيذها

واجهة الأمر والأوامر الملموسة

public interface ICommand
{
    void Execute();
}

public class TurnOnCommand : ICommand
{
    private Light _light;
    
    public TurnOnCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOn();
}

public class TurnOffCommand : ICommand
{
    private Light _light;
    
    public TurnOffCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOff();
}

المستقبل (Receiver)

public class Light
{
    public void TurnOn() => Console.WriteLine("Light is ON");
    public void TurnOff() => Console.WriteLine("Light is OFF");
}

المستدعي (Invoker)

public class RemoteControl
{
    private ICommand _command;
    
    public void SetCommand(ICommand command) => _command = command;
    
    public void PressButton() => _command.Execute();
}

الاستخدام

var light = new Light();
var remote = new RemoteControl();

remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();  // الضوء يعمل

remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();  // الضوء مطفأ

نمط الأمر مفيد لتنفيذ خاصية التراجع، أو تسجيل الماكرو، أو وضع العمليات في قائمة انتظار للتنفيذ لاحقاً. يغلف كل أمر كل ما هو مطلوب لتنفيذ إجراء ما، مما يجعل الأنظمة أكثر مرونة وقابلية للتوسع.

جرّب بنفسك

using System;
using TextEditor;

// TODO: إنشاء فئة Editor (المستدعي - invoker)
public class Editor
{
    // TODO: إضافة حقل خاص (private field) لتخزين الأمر الحالي
    private ICommand _command;

    // TODO: تنفيذ طريقة SetCommand
    public void SetCommand(ICommand command)
    {
        // TODO: تخزين الأمر
    }

    // TODO: تنفيذ طريقة ExecuteCommand
    public void ExecuteCommand()
    {
        // TODO: تنفيذ الأمر الحالي
    }
}

class Program
{
    public static void Main(string[] args)
    {
        // قراءة عدد الأوامر
        int n = Convert.ToInt32(Console.ReadLine());

        // إنشاء المستند (المستقبل - receiver)
        Document document = new Document();

        // إنشاء المحرر (المستدعي - invoker)
        Editor editor = new Editor();

        // TODO: معالجة كل أمر
        for (int i = 0; i < n; i++)
        {
            string commandType = Console.ReadLine();

            // TODO: بناءً على commandType، قم بإنشاء الأمر المناسب
            // - إذا كان "insert": اقرأ النص وأنشئ InsertCommand
            // - إذا كان "delete": اقرأ العدد وأنشئ DeleteCommand
            // ثم قم بتعيين الأمر في المحرر وتنفيذه
        }

        // TODO: طباعة المحتوى النهائي بالتنسيق "Content: {content}"
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming