הקוד:
ה abstract factory pattern מראה איך לפתור את בעיית ה
flexibility ביצירת המרכיבים של המבוך כאשר הקוד לא בהכרח יהיה יותר קטן.
שימוש בה יקל עלינו לשנות מרכיבים שונים של המבוך. ולכן נראה איך שימוש בה בפונקציה
CreateMaze() מפשט ומאפשר ליצור בקלות סוגים חדשים של מבוך.
נניח שנרצה להשתמש בקוד הקיים עבור משחק חדש עם מבוך מכושף... יהיו לו מרכיבים נוספים כמו דלת שדורשת כישוף וכו'.
איך נשנה את CreateMaze() בקלות כך שתיצור מבוך מהסוג החדש?
פשוט מאוד: ניישם את מה שלמדנו עד עתה בשקידה רבה ונשתמש ב...נכון! ב abstract factory pattern!
על ידי העברה של Factory כפרמטר ל CreateMaze() נוכל לקבוע את סוג האובייקטים שיווצרו.
ניצור מחלקה
MazeFactory
שתייצר מרכיבים של מבוך. היא תבנה דלתות, חדרים וקירות ביניהם. כל תוכנית שתרצה לבנות
מבוך תקח MazeFactory כפרמטר, וע"י קביעת סוג ה MazeFactory שהיא תקבל (MazeFactory עצמו הוא מחלקה
אבסטרקטית) יקבע הסוג של המבוך שיווצר על כל מרכיביו. כלומר: צריך לבחור בנקודה אחת בקוד איזה סוג של מבוך רוצים וה
Factory כבר ידאג לכל השאר, כמו שאנחנו אוהבים...
אם כן נראה איך זה מתבטא בקוד:
class MazeFactory {
public:
MazeFactory();
virtual Maze* MakeMaze() const
{ return new Maze; }
virtual Wall* MakeWall() const
{ return new Wall; }
virtual Room* MakeRoom (int n) const
{ return new Room (n); }
virtual Door* MakeDoor (Room* r1, Room* r2) const
{ return new Door (r1, r2); }
};
(כאן MazeFactory אינו מחלקה אבסטרקטית ולכן יכול לשמש כ Factory בעצמו)
כעת נראה גרסה של CreateMaze() שמנצלת את השימוש ב MazeFactory אשר מגיעה אליה כפרמטר (השינויים מסומנים בצבע שונה) :
Maze* MazeGame::CreateMaze (MazeFactory& factory) {
Maze* aMaze = factory.MakeMaze();
Room* r1 = factory.MakeRoom(1);
Room* r2 = factory.MakeRoom(2);
Door* aDoor = factory.MakeDoor(r1, r2);
aMaze->AddRoom(r1);
aMaze->AddRoom(r2);
r1->SetSide(North, factory.MakeWall());
r1->SetSide(East, aDoor);
r1->SetSide(South, factory.MakeWall());
r1->SetSide(Wast, factory.MakeWall());
r2->SetSide(North, factory.MakeWall());
r2->SetSide(East, factory.MakeWall());
r2->SetSide(South, factory.MakeWall());
r2->SetSide(West, aDoor);
return aMaze;
}
כעת, נניח שמאוד ישעמם לנו ופתאום נרצה ליצור מבוך מכושף. נגדיר subclass ל MazeFactory, שייקרא EnchantedMazeFactory, ושידרוס את
הפעולות הרלוונטיות ב MazeFactory ויחזיר subclasses שונים ל Room, Door וכו'.
class EnchantedMazeFactory : public MazeFactory {
public:
EnchantedMazeFactory();
virtual Room* MakeRoom(int n) const
{ return new EnchantedRoom(n, CastSpell()); }
virtual Door* MakeDoor (Room* r1, Room* r2) const
{ return new DoorNeedingSpell(r1, 42); }
protected:
Spell* CastSpell() const;
};
(DoorNeedingSpell ו EnchantedRoom הם כמובן subclass של Room, Door בהתאמה)
כעת, נניח שישעמם לנו עוד יותר: נניח שנרצה לאפשר סוג של חדר שיש בתוכו פצצה. אם היא מתפוצצת היא גורמת לנזק לקירות. אפשר
ליצור subclass של Room שיוכל לבדוק אם יש פצצה בחדר ואם היא התפוצצה, ו subclass של Wall שיוכל לבדוק אם הוא נפגע כתוצאה
מפיצוץ. כמו כן נבנה BombedMazeFactory שהוא subclass של MazeFactory שיבטיח לנו שהקירות והחדרים יהיו מהסוג המתאים. נדרוס
שתי פעולות:
Wall* BombdMazeFactory::MakeWall () const {
Return new BombedWall;
}
Room* BombedMazeFactory::MakeRoom (int n) const {
Return new RoomWithABomb (n);
}
כעת כדי לבנות מבוך עם פצצות בסה"כ נקרא ל CreateMaze() עם BombedMazeFactory כפרמטר:
MazeGame game; //this code always the same
BombedMazeFactory factory;
game.CreateMaze(factory); //this code always the same
הקוד באדום זוהי הנקודה היחידה (!) בתוכנית בה אנו קובעים עם אילו אובייקטים נעבוד.
אם נרצה לבנות מבוך מכושף פשוט נקרא ל CreateMaze() עם EnchantedMazeFactory כפרמטר, וכן הלאה מבוכים ממבוכים שונים כיד
הדמיון הטובה עליכם...
הערה: יתכן ובחלק מהמבוכים השונים יש פעולות שספציפיות לאותו מבוך ולכן לא ניתן לגשת אליהם מהממשק המשותף, לדוגמה: בחדר עם
פצצה יש פעולה ספציפית לו לבדוק האם היא התפוצצה. פעולה זו אינה קיימת בממשק של המחלקה חדר. כדי לגשת לפעולה זו יש צורך ב
dynamic_cast וכאן מתגלה יתרון נוסף של ה abstract factory pattern :
dynamic_cast עובד רק אם ה type של האובייקט הוא באמת זה שמצפים לו, ו abstract factory pattern אכן מבטיחה שכל המוצרים
המיוצרים בתוכנית הם מאותו סוג מכיוון שיש נקודה אחת בקוד שבה מושפע הסוג שלהם וזו נקודת בחירת הFactory . ה Factory שנבחר יכול
לייצר מוצרים מסוג אחד בדיוק ולכן לא יכול לקרות מצב שלפתע נתקלים בסוג שלא ציפינו לו ( מגניב !!!).
אני מקווה ש"קניתם" את ה abstract factory pattern כי זה אחד המוצרים היותר טובים בשוק.