نمط الأمر هو واحد من أنماط التصميم السلوكي. يتم استخدام نمط التصميم الأمر لتنفيذ الربط الضعيف في نموذج الطلب-الرد.
نمط الأمر
في نمط الأمر، يتم إرسال الطلب إلى المُستدعي
invoker
ومن ثم يمرره المُستدعي إلى كائن الأمر command
المغلف. يمرر كائن الأمر الطلب إلى الطريقة المناسبة من المستلم Receiver
لتنفيذ الإجراء الخاص. ينشئ برنامج العميل كائن المستلم ثم يربطه بالأمر. ثم ينشئ عنصر المستدعي ويربطه بكائن الأمر لتنفيذ إجراء. الآن عندما ينفذ برنامج العميل الإجراء، يتم معالجته بناءً على الأمر وكائن المستلم.
مثال على نمط تصميم الأمر
سنلقي نظرة على سيناريو حقيقي حيث يمكننا تنفيذ نمط الأمر. فلنقل أننا نريد توفير أداة لنظام الملفات بأساليب لفتح وكتابة وإغلاق الملف. يجب أن تدعم هذه الأداة لنظام الملفات عدة أنظمة تشغيل مثل ويندوز ويونكس. لتنفيذ أداة نظام الملفات الخاصة بنا ، نحتاج أولاً إلى إنشاء فئات المستلم المسؤولة عن القيام بجميع العمليات. نظرًا لأننا نبرمج بتقنية الواجهة في جافا ، يمكننا أن نمتلك واجهة FileSystemReceiver وفئات تنفيذها لأنظمة التشغيل المختلفة مثل ويندوز ويونكس وسولاريس وما إلى ذلك.
فئات المستلم لنمط الأمر
package com.journaldev.design.command;
public interface FileSystemReceiver {
void openFile();
void writeFile();
void closeFile();
}
واجهة FileSystemReceiver تحدد العقدة لفئات التنفيذ. للبساطة ، سأقوم بإنشاء نوعين من فئات المستلم للعمل مع أنظمة يونكس وويندوز.
package com.journaldev.design.command;
public class UnixFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in unix OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in unix OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in unix OS");
}
}
package com.journaldev.design.command;
public class WindowsFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in Windows OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in Windows OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in Windows OS");
}
}
هل لاحظت تعليق Override وإذا كنت تتساءل عن سبب استخدامه ، يرجى قراءة تعليقات جافا و فوائد تعليق Override. الآن بعد أن أصبحت فئات المستلم جاهزة ، يمكننا المضي قدمًا في تنفيذ فئات الأمر.
واجهة نمط الأمر وتنفيذاته
يمكننا استخدام الواجهة أو الصنف المجرد لإنشاء الأمر الأساسي الخاص بنا، إنه قرار تصميمي يعتمد على متطلباتك. نحن نستخدم الواجهة لأنه ليس لدينا أي تنفيذات افتراضية.
package com.journaldev.design.command;
public interface Command {
void execute();
}
الآن نحتاج إلى إنشاء تنفيذات لجميع أنواع الإجراءات المختلفة التي يقوم بها المستقبل. نظرًا لأن لدينا ثلاثة إجراءات، سنقوم بإنشاء ثلاثة تنفيذات للأوامر. ستقوم كل تنفيذة للأمر بإعادة توجيه الطلب إلى الطريقة المناسبة للمستقبل.
package com.journaldev.design.command;
public class OpenFileCommand implements Command {
private FileSystemReceiver fileSystem;
public OpenFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
// الأمر المفتوح يقوم بإعادة توجيه الطلب إلى طريقة openFile
this.fileSystem.openFile();
}
}
package com.journaldev.design.command;
public class CloseFileCommand implements Command {
private FileSystemReceiver fileSystem;
public CloseFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.closeFile();
}
}
package com.journaldev.design.command;
public class WriteFileCommand implements Command {
private FileSystemReceiver fileSystem;
public WriteFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.writeFile();
}
}
الآن لدينا المستقبل وتنفيذات الأوامر جاهزة، لذا يمكننا المضي قدمًا في تنفيذ فئة المنادي.
فئة المنادي في نمط الأمر
المنادي هو فئة بسيطة تقوم بتجميع الأمر وتمرير الطلب إلى كائن الأمر لمعالجته.
package com.journaldev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
تنفيذ أداة نظام الملفات لدينا جاهز ويمكننا الانتقال إلى كتابة برنامج عميل بنمط الأمر البسيط. ولكن قبل ذلك، سأوفر طريقة فائدة لإنشاء كائن FileSystemReceiver
المناسب. نظرًا لأنه يمكننا استخدام فئة System للحصول على معلومات نظام التشغيل، سنستخدم هذا أو نستخدم نمط Factory لإرجاع النوع المناسب استنادًا إلى المدخل.
package com.journaldev.design.command;
public class FileSystemReceiverUtil {
public static FileSystemReceiver getUnderlyingFileSystem(){
String osName = System.getProperty("os.name");
System.out.println("Underlying OS is:"+osName);
if(osName.contains("Windows")){
return new WindowsFileSystemReceiver();
}else{
return new UnixFileSystemReceiver();
}
}
}
لننتقل الآن إلى إنشاء برنامج عميل مثالي بنمط الأمر الذي سيستهلك أداة نظام الملفات الخاصة بنا.
package com.journaldev.design.command;
public class FileSystemClient {
public static void main(String[] args) {
//إنشاء كائن المستقبل
FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
//إنشاء الأمر وربطه بالمستقبل
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
//إنشاء المُعرض وربطه بالأمر
FileInvoker file = new FileInvoker(openFileCommand);
//تنفيذ الإجراء على كائن المُعرض
file.execute();
WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
file = new FileInvoker(writeFileCommand);
file.execute();
CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
file = new FileInvoker(closeFileCommand);
file.execute();
}
}
لاحظ أن العميل مسؤول عن إنشاء نوع الأمر المناسب. على سبيل المثال، إذا كنت ترغب في كتابة ملف، فليس من المفترض أن تقوم بإنشاء كائن CloseFileCommand
. البرنامج العميل مسؤول أيضًا عن ربط المستقبل بالأمر ثم الأمر بفئة المُعرض. مخرج برنامج مثال النمط أعلاه هو:
Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS
مخطط الفئة لنمط الأمر
وهنا رسم الفئة لتنفيذ أداة نظام الملفات الخاصة بنا.
نقاط مهمة في نمط الأمر
- الأمر هو النواة في نمط تصميم الأمر الذي يحدد العقد للتنفيذ.
- تنفيذ المستقبل مفصول عن تنفيذ الأمر.
- فئات تنفيذ الأمر اختارت الطريقة لاستدعاء كائن المستقبل، لكل طريقة في المستقبل ستكون هناك تنفيذ أمر. يعمل كجسر بين المستقبل وطرق العمل.
- الفئة المحرضة مجرد توجيه الطلب من العميل إلى كائن الأمر.
- العميل مسؤول عن إنشاء الأمر المناسب وتنفيذ تنفيذ المستقبل ثم ربطهما معًا.
- العميل مسؤول أيضًا عن إنشاء كائن المحرض وربط كائن الأمر به وتنفيذ طريقة العمل.
- نمط تصميم الأمر قابل للتوسيع بسهولة، يمكننا إضافة طرق عمل جديدة في المستقبل وإنشاء تنفيذات أمر جديدة دون تغيير رمز العميل.
- العيب في نمط تصميم الأمر هو أن الكود يصبح ضخمًا ومربكًا مع عدد كبير من طرق العمل وبسبب العديد من الارتباطات.
مثال لنمط تصميم الأوامر باستخدام JDK
واجهة Runnable (java.lang.Runnable) و Swing Action (javax.swing.Action) تستخدم نمط الأوامر.
Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern