תבנית הפקודה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 96 מתוך 104.
תבנית Command עוטפת בקשה כאובייקט, ומאפשרת להתאים לקוחות עם בקשות שונות, לתור פעולות או לתמוך בפונקציונליות של ביטול. היא מנתקת בין האובייקט שמפעיל פעולה לבין האובייקט שיודע כיצד לבצע אותה.
Command עם מתודה execute(), פקודות קונקרטיות שמממשות פעולות מסוימות, מקבל שמבצע את העבודה בפועל, ומפעיל שמפעיל פקודות:
#include <iostream>
#include <memory>
#include <vector>
// המקבל - יודע כיצד לבצע פעולות
class Light {
public:
void turnOn() { std::cout << "Light is ON\n"; }
void turnOff() { std::cout << "Light is OFF\n"; }
};
// ממשק Command
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
// פקודות קונקרטיות
class LightOnCommand : public Command {
Light& light;
public:
LightOnCommand(Light& l) : light(l) {}
void execute() override { light.turnOn(); }
void undo() override { light.turnOff(); }
};
class LightOffCommand : public Command {
Light& light;
public:
LightOffCommand(Light& l) : light(l) {}
void execute() override { light.turnOff(); }
void undo() override { light.turnOn(); }
};
// המפעיל
class RemoteControl {
std::vector<std::unique_ptr<Command>> history;
public:
void pressButton(std::unique_ptr<Command> cmd) {
cmd->execute();
history.push_back(std::move(cmd));
}
void pressUndo() {
if (!history.empty()) {
history.back()->undo();
history.pop_back();
}
}
};כל פקודה שומרת הפניה למקבל שלה וקוראת למתודה המתאימה כשהיא מבוצעת. המפעיל אינו יודע איזו פעולה תתרחש — הוא פשוט קורא ל־execute(). על ידי שמירת פקודות בהיסטוריה, אפשר לממש בקלות ביטול באמצעות קריאה למתודת undo() של כל פקודה.
השתמשו ב-Command כשצריך להכניס פעולות לתור, לממש ביטול וביצוע מחדש, או לנתק בין שולח הבקשה למטפל בה.
אתגר
קלבואו נבנה עורך טקסט עם אפשרות לבטל פעולות, באמצעות תבנית הפקודה (Command). תיצרו מערכת שבה פעולות עריכה (כמו הוספה ומחיקה של טקסט) מאוגדות כאובייקטים של פקודה, וכך המשתמשים יוכלו לבצע פעולות ואז לבטל אותן בסדר הפוך, ממש כמו בעורך טקסט אמיתי.
תארגנו את הקוד שלכם בארבעה קבצים:
TextDocument.h: צרו את מחלקת המקבל שמחזיקה את תוכן הטקסט עצמו ומבצעת בו שינויים.מחלקת
TextDocumentשלכם צריכה לשמור את תוכן המסמך כמחרוזת. ממשו את המתודות הבאות:insertText(const std::string& text): מוסיפה טקסט לסוף התוכןdeleteText(int count): מסירה אתcountהתווים האחרונים מהתוכן (לא עושה דבר אם count גדול מאורך התוכן)getContent(): מחזירה את התוכן הנוכחי כייחוס קבוע
Command.h: הגדירו את ממשק הפקודה ואת מחלקות הפקודות המוחשיות.צרו מחלקת
Commandמופשטת עם מתודות וירטואליות טהורותexecute()ו-undo(), וגם מפרק וירטואלי.לאחר מכן ממשו שתי פקודות מוחשיות:
InsertCommand: מקבלת ייחוס ל-TextDocumentומחרוזת להוספה. בעת ביצועה, היא מוסיפה את הטקסט. בעת ביטולה, היא מוחקת את אותו מספר תווים שהוכנסו.DeleteCommand: מקבלת ייחוס ל-TextDocumentומספר תווים למחיקה. עליה לשמור את הטקסט שנמחק כדי שתוכל לשחזר אותו בעת ביטול הפעולה. בעת ביצועה, היא מסירה תווים מהסוף. בעת ביטולה, היא מוסיפה מחדש את הטקסט השמור.
TextEditor.h: צרו את המחלקה המזמנת שמנהלת את ביצוע הפקודות ואת ההיסטוריה.מחלקת
TextEditorשלכם צריכה להחזיק ייחוס ל-TextDocumentולנהל היסטוריה של פקודות שבוצעו, באמצעות וקטור שלunique_ptr<Command>. ממשו:executeCommand(std::unique_ptr<Command> cmd): מבצעת את הפקודה ומוסיפה אותה להיסטוריהundo(): מבטלת את הפקודה האחרונה ומסירה אותה מההיסטוריה (לא עושה דבר אם ההיסטוריה ריקה)showContent(): מדפיסה את תוכן המסמך הנוכחי, או[empty]אם התוכן ריק
main.cpp: הדגימו את תבנית הפקודה עם אפשרות לבטל פעולות.קראו שלושה קלטים:
- הטקסט הראשון להוספה (מחרוזת)
- הטקסט השני להוספה (מחרוזת)
- מספר התווים למחיקה (מספר שלם)
צרו
TextDocumentו-TextEditor. לאחר מכן בצעו את הפעולות הבאות לפי הסדר, והציגו את התוכן לאחר כל שלב:- הוסיפו את הטקסט הראשון, ולאחר מכן הציגו את התוכן
- הוסיפו את הטקסט השני, ולאחר מכן הציגו את התוכן
- מחקו את מספר התווים שצוין, ולאחר מכן הציגו את התוכן
- בטלו פעולה אחת, ולאחר מכן הציגו את התוכן
- בטלו פעולה נוספת, ולאחר מכן הציגו את התוכן
לדוגמה, עם הקלטים Hello, World ו-3:
Hello
Hello World
Hello Wo
Hello World
Helloעם הקלטים Code, Editor ו-6:
Code
CodeEditor
Code
CodeEditor
Codeשימו לב שכל פקודה יודעת כיצד להפוך את הפעולה שלה. DeleteCommand זוכרת מה היא מחקה כדי שתוכל לשחזר אותו, ו-InsertCommand יודעת בדיוק כמה תווים להסיר בעת ביטול הפעולה. זהו כוחה של תבנית הפקודה: פעולות הופכות לאובייקטים עצמאיים שאפשר לשמור, לבצע ולהפוך.
נסו בעצמכם
#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"
int main() {
// קראו את הקלטים
std::string firstText;
std::string secondText;
int deleteCount;
std::getline(std::cin, firstText);
std::getline(std::cin, secondText);
std::cin >> deleteCount;
// TODO: צרו TextDocument ו-TextEditor
// TODO: בצעו את הפעולות לפי הסדר, והציגו את התוכן אחרי כל פעולה:
// 1. הכניסו את הטקסט הראשון, ואז הציגו את התוכן
// 2. הכניסו את הטקסט השני, ואז הציגו את התוכן
// 3. מחקו את מספר התווים שצוין, ואז הציגו את התוכן
// 4. בטלו את הפעולה האחרונה פעם אחת, ואז הציגו את התוכן
// 5. בטלו פעולה נוספת, ואז הציגו את התוכן
// רמז: השתמשו ב-std::make_unique<InsertCommand>(...) וב-
// std::make_unique<DeleteCommand>(...) כדי ליצור פקודות
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית השיטה התבניתיתתבנית המצבתבנית המורכבRAII כתבנית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין