نمط الأمر
جزء من قسم 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" قويًا بشكل خاص لتنفيذ وظائف التراجع، أو تسجيل الماكرو، أو وضع العمليات في طابور للتنفيذ لاحقًا. يقوم كل أمر بتغليف كل ما هو مطلوب لتنفيذ إجراء ما، مما يجعل نظامك أكثر مرونة وقابلية للتوسع.
التحدي
سهللنقم ببناء نظام أوامر لمحرر نصوص باستخدام نمط الأمر (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}"
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection