תבנית עיצוב פקודה

התבנית של הפקודה היא אחת מהתבניות של עיצוב ההתנהגות. תבנית עיצוב הפקודה משמשת ליישום חיבור פתוח בדוגמת תגובה לבקשה.

תבנית הפקודה

בתבנית של הפקודה, הבקשה נשלחת ל-invoker וה- invoker מעביר אותה לאובייקט המאוחסן של ה- command. אובייקט הפקודה מעביר את הבקשה לשיטה המתאימה של Receiver כדי לבצע את הפעולה הספציפית. התוכנית לקוח יוצרת את אובייקט הנמען ולאחר מכן מצרפת אותו לפקודה. לאחר מכן, היא יוצרת את אובייקט ה-invoker ומצרפת את אובייקט הפקודה כדי לבצע פעולה. כעת כאשר תוכנית הלקוח מפעילה את הפעולה, היא מעובדת על פי הפקודה ואובייקט הנמען.

דוגמת תבנית עיצוב הפקודה

נחקור סצנה ממשיית בה נוכל ליישם את תבנית הפקודה. נניח שאנו רוצים לספק שימוש בתוכנה למערכת קובץ עם שיטות לפתיחה, כתיבה וסגירה של קובץ. יש לנו גם דרישה שהתוכנה תתמוך במספר מערכות הפעלה שונות כמו Windows ו-Unix. כדי ליישם את יישום המערכת שלנו, נצטרך תחילה ליצור מחלקות מקבל (Receiver) היכולות לבצע את כל העבודה. בהתאם לקוד שלנו בעזרת ממשק ב-Java, נוכל ליצור ממשק FileSystemReceiver ומחלקות הממשק עבור טעמי מערכת ההפעלה השונים כמו Windows, Unix ו-Solaris וכדומה.

מחלקות מקבל בתבנית הפקודה

package com.journaldev.design.command;

public interface FileSystemReceiver {

	void openFile();
	void writeFile();
	void closeFile();
}

ממשק FileSystemReceiver מגדיר את החוזה עבור מחלקות המימוש. לפשטות, אני יוצר שני סוגים של מחלקות מקבל שיעבדו עם מערכות Unix ו-Windows.

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");
	}

}

האם שם לבתם להערכת העקיבה ואם אתם סקרנים למה הוא בשימוש, קראו אנוטציות Java ו-היתרונות של אנוטציית 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 כדי לקבל מידע על מערכת ההפעלה, נשתמש בזה או נשתמש בתבנית המפענח כדי להחזיר סוג מתאים בהתאם לקלט.

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 של תבנית עיצוב של Command

ממשק Runnable (java.lang.Runnable) ופעולת Swing (javax.swing.Action) משתמשים בתבנית הפקודה.

Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern