טיפ ג'אווה 68: למד כיצד ליישם את דפוס הפקודה ב- Java

דפוסי עיצוב לא רק מאיצים את שלב התכנון של פרויקט מונחה עצמים (OO) אלא גם מגבירים את התפוקה של צוות הפיתוח ואיכות התוכנה. פיקוד דפוס הוא דפוס התנהגות אובייקט המאפשר לנו להשיג decoupling מוחלטת בין השולח למקבל. ( שולח הוא אובייקט שמפעיל פעולה, ומקלט הוא אובייקט שמקבל את הבקשה לביצוע פעולה מסוימת. עם ניתוק, אין לשולח שום ידיעה על Receiverהממשק של הממשק.) המונח בקשהכאן מתייחס לפקודה שיש לבצע. דפוס הפיקוד מאפשר לנו גם להשתנות מתי ואיך ממלאים את הבקשה. לכן, דפוס פיקוד מספק לנו גמישות כמו גם הרחבה.

בשפות תכנות כמו C, מצביעי פונקציות משמשים לחיסול הצהרות מתגים ענקיות. (ראה "טיפ ג'אווה 30: פולימורפיזם וג'אווה" לקבלת תיאור מפורט יותר.) מכיוון שלג'אווה אין מצביעי פונקציה, אנו יכולים להשתמש בתבנית הפקודה ליישום שיחות חוזרות. תראה זאת בפעולה בדוגמת הקוד הראשונה שלמטה, הנקראת TestCommand.java.

מפתחים שרגילים להשתמש במצביעי פונקציה בשפה אחרת עשויים להתפתות להשתמש Methodבאובייקטים של ה- Reflection API באותו אופן. לדוגמא, במאמרו "Java Reflection", פול טרמבלט מראה לך כיצד להשתמש ב- Reflection ליישום עסקאות מבלי להשתמש בהצהרות מתג. עמדתי בפיתוי זה, מכיוון ש- Sun ממליצה להשתמש בממשק ה- Reflection כאשר כלים אחרים הטבעיים יותר לשפת התכנות של Java יספיקו. (ראה משאבים לקישורים למאמר של טרמבלט ולדף ההדרכה של Sun.) קל יותר לנקות באגים ולתחזק את התוכנית שלך אם אינך משתמש Methodבאובייקטים. במקום זאת, עליך להגדיר ממשק וליישם אותו בשיעורים המבצעים את הפעולה הנדרשת.

לכן, אני מציע לך להשתמש בתבנית Command בשילוב עם מנגנון הטעינה והקישור הדינמי של Java כדי ליישם מצביעי פונקציות. (לפרטים אודות מנגנון הטעינה והקישור הדינמי של ג'אווה, ראה ג'יימס גוסלינג והנרי מקגילטון "סביבת שפת Java - ספר לבן", המופיע במשאבים.)

על ידי ביצוע ההצעה לעיל, אנו מנצלים את הפולימורפיזם המסופק על ידי יישום דפוס פיקוד לחיסול הצהרות מתגים ענקיות, וכתוצאה מכך ניתן להרחיב מערכות. אנו מנצלים גם את מנגנוני הטעינה והקישור הייחודיים של Java כדי לבנות מערכת דינמית וניתנת להרחבה דינמית. זה מתואר בדוגמא הקוד הקוד לדוגמא להלן, הנקראת TestTransactionCommand.java.

דפוס הפיקוד הופך את הבקשה עצמה לאובייקט. ניתן לאחסן אובייקט זה ולהעביר אותו כמו חפצים אחרים. המפתח לתבנית זו הוא Commandממשק, שמכריז על ממשק לביצוע פעולות. בצורתו הפשוטה ביותר, ממשק זה כולל executeפעולה מופשטת . כל Commandמחלקת בטון מציינת זוג מקלט-פעולה על ידי אחסון Receiverהמשתנה למשל. הוא מספק יישומים שונים של execute()השיטה כדי להפעיל את הבקשה. Receiverיש את הידע הדרוש כדי לבצע את הבקשה.

איור 1 להלן מציג את Switch- צבירת Commandאובייקטים. יש לו flipUp()ואת flipDown()פעילות הממשק שלה. Switchנקרא הפונה מכיוון שהוא קורא לפעולת הביצוע בממשק הפקודה.

הפקודה הקונקרטית LightOnCommand, מיישמת את executeפעולת ממשק הפקודה. יש לו את הידע לקרוא Receiverלפעולה של האובייקט המתאים . זה משמש כמתאם במקרה זה. על ידי מונח המתאם, אני מתכוון כי בטון Commandהאובייקט הוא מחבר פשוט, חיבור Invokerואת Receiverעם ממשקים שונים.

הלקוח instantiates Invoker, את Receiver, ואת הפקודה בטון אובייקטים.

איור 2, תרשים הרצף, מציג את האינטראקציות בין האובייקטים. זה ממחיש כיצד Commandמנותק את Invokerה Receiver(ואת הבקשה שהוא מבצע). הלקוח יוצר פקודה קונקרטית על ידי פרמטר של הבנאי שלו עם המתאים Receiver. ואז הוא מאחסן Commandאת Invoker. Invokerשיחות לגבות את הפקודה בטון, אשר יש לו את הידע לבצע את הרצוי Action()הפעולה.

הלקוח (התוכנית הראשית ברישום) יוצר Commandאובייקט קונקרטי וקובע אותו Receiver. בתור Invokerאובייקט, Switchמאחסן את הבטון Commandהאובייקט. Invokerמנפיק בקשה ידי התקשרות executeעל Commandהאובייקט. Commandהאובייקט הקונקרטי קורא לפעולות על Receiverמנת לבצע את הבקשה.

הרעיון המרכזי כאן הוא שהפקודה הקונקרטית רושמת את עצמה עם Invokerה- Invokerוקוראת אותה בחזרה, ומבצעת את הפקודה ב- Receiver.

קוד לדוגמא לדפוס פקודה

בואו נסתכל על דוגמה פשוטה הממחישה את מנגנון ההחזרה המושג באמצעות דפוס הפקודה.

הדוגמה מראה a Fanו- a Light. המטרה שלנו היא לפתח Switchאובייקט שיכול להפעיל או לכבות את האובייקט. אנו רואים כי Fanואת Lightיש ממשקים שונים, כלומר Switchצריך להיות עצמאית של Receiverממשק או אין לו את הידע של קוד> ממשק של המקלט. כדי לפתור בעיה זו, עלינו לפרמט את כל אחד מה- Switchs באמצעות הפקודה המתאימה. ברור Switchשלמחובר Lightלרצון תהיה פקודה שונה מזו Switchהמחוברת ל Fan. Commandבכיתה צריכה להיות מופשט או ממשק כדי שזה יעבוד.

כאשר Switchמופעל הקונסטרוקטור של a , הוא מתבצע פרמטרים באמצעות קבוצת הפקודות המתאימה. הפקודות יאוחסנו כמשתנים פרטיים של ה- Switch.

כאשר נקראים פעולות flipUp()ו- flipDown(), הם פשוט מבצעים את הפקודה המתאימה execute( ). לא Switchיהיה מושג מה יקרה כתוצאה execute( )מכינויו.

מחלקה TestCommand.java מאוורר {public void startRotate () {System.out.println ("המאוורר מסתובב"); } public void stopRotate () {System.out.println ("המאוורר אינו מסתובב"); }} מחלקה Light {public void turnOn () {System.out.println ("האור דולק"); } בטל ציבורי בטל () {System.out.println ("האור כבוי"); }} מחליף מחלקה {פרטי Command UpCommand, DownCommand; מתג ציבורי (Command Up, Command Down) {UpCommand = למעלה; // פיקוד קונקרטי רושם את עצמו אצל הפונה DownCommand = Down; } void flipUp () {// invoker קורא בחזרה Command command, שמבצע את הפקודה ב- UpCommand המקלט. לבצע ( ) ; } בטל flipDown () {DownCommand. לבצע ( ); }} מחלקה LightOnCommand מיישמת את הפקודה {Private Light myLight; LightOnCommand ציבורי (אור L) {myLight = L;} לבצע חלל ציבורי () {myLight. להדליק( ); }} מחלקה LightOffCommand מיישמת את Command {private Light myLight; LightOffCommand ציבורי (אור L) {myLight = L; } לבצע חלל ציבורי () {myLight. לכבות( ); }} מחלקה FanOnCommand מיישמת את הפקודה {Fan Fan myFan; FanOnCommand ציבורי (מאוורר F) {myFan = F; } לבצע חלל ציבורי () {myFan. startRotate (); }} מחלקה FanOffCommand מיישמת את הפקודה {Fan Fan private; FanOffCommand ציבורי (מאוורר F) {myFan = F; } לבצע חלל ציבורי () {myFan. stopRotate (); }} מחלקה ציבורית TestCommand {public static void main (String [] args) {Light testLight = new light (); LightOnCommand testLOC = LightOnCommand חדש (testLight); LightOffCommand testLFC = LightOffCommand חדש (testLight); החלף testSwitch = מתג חדש (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown ();אוהד testFan = מאוורר חדש (); FanOnCommand foc = FanOnCommand חדש (testFan); FanOffCommand ffc = חדש FanOffCommand (testFan); החלף ts = מתג חדש (foc, ffc); ts.flipUp (); ts.flipDown (); }} ממשק ציבורי Command.java Command {תקציר ריק ריק לבצע (); }

שים לב בדוגמת הקוד לעיל כי דפוס הפקודה מבטל לחלוטין את האובייקט שמפעיל את הפעולה - (Switch )- מאלה שיש להם את הידע לבצע אותו - Lightו Fan. זה נותן לנו הרבה גמישות: על האובייקט שמוציא בקשה לדעת רק להנפיק אותה; אין צורך לדעת כיצד תתבצע הבקשה.

דפוס פיקוד ליישום עסקאות

דפוס פיקוד ידוע גם בשם תבנית פעולה או עסקה. הבה נבחן שרת המקבל ומעבד עסקאות המועברות על ידי לקוחות באמצעות חיבור שקע TCP / IP. עסקאות אלה כוללות פקודה, ואחריה אפס ויותר ארגומנטים.

מפתחים עשויים להשתמש בהצהרת מתג עם מקרה לכל פקודה. שימוש Switchבהצהרות במהלך קידוד הוא סימן לעיצוב גרוע בשלב התכנון של פרויקט מונחה עצמים. פקודות מייצגות דרך מונחית עצמים לתמוך בעסקאות וניתן להשתמש בהן כדי לפתור בעיית תכנון זו.

בקוד הלקוח של התוכנית TestTransactionCommand.java, כל הבקשות מכוסות TransactionCommandבאובייקט הגנרי . TransactionCommandהבנאים נוצרו על ידי לקוח והוא רשום CommandManager. ניתן לבצע את הבקשות בתור בזמנים שונים על ידי התקשרות ל- runCommands(), מה שמעניק לנו הרבה גמישות. זה גם נותן לנו את היכולת להרכיב פקודות לפקודה מורכבת. יש לי גם CommandArgument, CommandReceiver, ו CommandManagerכיתות ו subclasses של TransactionCommand- כלומר AddCommandו SubtractCommand. להלן תיאור של כל אחד מהשיעורים הללו:

  • CommandArgumentהוא מעמד עוזר, המאחסן את טיעוני הפקודה. ניתן לשכתב אותו כדי לפשט את משימת העברת מספר גדול או משתנה של ארגומנטים מכל סוג שהוא.

  • CommandReceiver מיישם את כל שיטות עיבוד הפקודה ומיושם כדפוס סינגלטון.

  • CommandManagerהוא המזמין והוא Switchהמקבילה מהדוגמה הקודמת. הוא מאחסן את TransactionCommandהאובייקט הגנרי myCommandבמשתנה הפרטי שלו . כאשר runCommands( )הוא מופעל, הוא מכנה execute( )את TransactionCommandהאובייקט המתאים .

בג'אווה ניתן לחפש את ההגדרה של מחלקה בהינתן מחרוזת המכילה את שמה. בשנות execute ( )פעולתו של TransactionCommandהכיתה, ואני מחשב את שם הכיתה דינמי ולקשר אותו לתוך המערכת פועלת - כלומר, כיתות נטענים במהירות הבזק כנדרש. אני משתמש במוסכמת השמות, שם הפקודה המשורשר במחרוזת "פקודה" כשם תת-מחלקת פקודת העסקה, כך שניתן יהיה לטעון אותו באופן דינמי.

שים לב Classשהאובייקט שהוחזר על ידי ה- newInstance( )חייב להיות יצוק לסוג המתאים. המשמעות היא שהמחלקה החדשה צריכה ליישם ממשק או לסווג מחלקה קיימת שמוכרת לתוכנית בזמן הידור. במקרה זה, מכיוון שאנו מיישמים את Commandהממשק, זו אינה בעיה.