נניח ואנו רוצים לפתח משחק קלפים.
עבור משחק זה נרצה כמה דברים: ריבוי משתתפים, לכל שחקן תהיה כמות כסף וכמות חיים וכו'.
האם אנו מתחילים ישר בקוד?
אנו נתחיל בלעצב אותו באמצעות שזה ראשי תיבות של שהיא הדרך הסטנדרטית להסתכל על עיצוב תוכנה ללא בחירת שפת תכנות.
יש פרק גדול יותר בהמשך על אבל כרגע נגדיר את הדיאגרמת המחלקות ודיאגרמת השלבים()
דיאגרמת מחלקות
דיאגרמת המחלקות מתארת את המצב הסטטי של מבנה התוכנה, היא מראה את המחלקות והפרטים שלהן.
עבור המחלקה:
class Player
{
double healthPoints;
double money;
public:
void playCard(const Card& card);
};
נקבל את ה- הבא:
עבור כמה מחלקות, נניח:
class Card
{
double price;
double healthGain;
};
class CardGame
{
Player* currentPlayer;
Array<Player> players;
Array<Card> cards;
public:
void playTurn();
};
בנוסף למחלקה ממקודם, אזי נקבל:
ניתן להשמיט שדות שאנו רואים בדיאגרמה כך:
שימוש בדיאגרמת מחלקות
נניח והיינו רוצים לבנות תוכנה, לפי מה שראינו בתחילת הקורס נבצע את זה במחזורים.
אנו רוצים לבנות תוכנה לניהול עובדים ובמחזור הפיתוח הראשון נפתח את:
- כל העובדים.
- לכל עובד יש שם, תאריך לידה ושכר בסיסי.
- יש שני סוגים של עובדים: מהנדס ואיש מכירות.
- חישוב הוצאות שנתיות.
- העלה או בונוס לעבודים כאשר לכל סוג עובד יש דרך שונה להביא בונוס.
נסתכל על ה- של היצור הזה:
לאחר שסיימנו ליצור את זה, אנו עוברים למחזור השני והשלישי וכך הלאה.
דיאגרמת רצף
דיאגרמת רצף מתארת את הזרימה הדינמית של מערכת.

האדם העומד בהתחלה נקרא והוא מגדיר את המשתמש או מערכת אחרת או מכשיר אשר משתמש במערכת שלנו.
הדיאגרמה הזו מתארת תהליך זרימה אחד במערכת, כאשר בדרך כלל אנו נתאר את התהליכים המרכזיים בכמה דיאגרמות רצף.
זה שימושי לתאר באג במערכת.
תבניות עיצוב תוכנה
למה אנו מסתכים על תבניות עיצוב?
- כי זה נותן לנו שפה משותפת.
- כדי שלא נדרש להמציא את הגלגל מחדש.
- נותן רמז לפתרון של בעיה
- משפר את עיצוב המערכת
ישנם שלושה סוגים של תבניות עיצוב:
- התנהגותיות: מתעסק בתקשורת בין מבנים שונים.
דוגמה: איטרטורים, . - מבנה: מתעסק עם חיבור של מבנים ואובייקטים ואיחודם לתוכנה אחת.
דוגמה: . - יצירתי: מתעסק עם יצירה וגישה למבנים.
דוגמה: .
אנוחנו כבר ראינו את זה והוא מתאר היררכיה.
המקרה הכללי ביותר הינו:
תבניות עיצוב התנהגותיות
כמעט בכל הפרק הזה נסתכל על דוגמה של משחק פוקר בו השחקן יכול לבצע את הפעולות .
יהיו לנו את המחלקות הבאות:
class Player
{
public:
string getName();
int getMoney();
void makeMove(Game& game);
};
class Game
{
Player *players;
Player *currentPlayer;
public:
void fold();
void call();
void raise(int amount);
};
אנו נוסיף מחשב עם סוגי ההתנהגויות הבאות: וכו'.
כל שחקן מממש את בתצורה מעט שונה ⟸ מה אנו צריכים לעשות עם ?
- נוכל לשים אשר יכיל את סוגי המשחק ⟸ זה כמובן לא טוב במיוחד כי זה דורש הרבה התעסקות.
- נוכל להפוך את השחקן לאבסטרקטי ולרשת ממנו לכל סוג שחקן, אך מה אם נרצה לשנות את סוג המשחק שלנו על בסיס אירועים במשחק? לא ניתן לרשת מכמה מחלקות(ניתן בעצם אבל לא בקורס הזה)
תבנית עיצוב
הפתרון שנבחר הוא לבצע ל- ולהוציא את ההתנהגות ממנו כך:
לדבר הזה קוראים באופן כללי אסטרטגיה.
המימוש של זה יראה כך:
class Behaviour
{
virtual void makeMove(Game& game) = 0;
};
class Aggressive : public Behaviour
{
public:
void makeMove(Game game&) override;
};
class Player
{
Behaviour *behaviour;
public:
void setBehaviour(Behaviour *behaviour) { this->behaviour = behaviour; }
void makeMove(Game& game) { behaviour->makeMove(game); }
};
תבנית עיצוב
הרעיון ב- זה לקחת שיטה או פונקציה ולהפוך אותה לאובייקט.
בדוגמה שלנו נרצה כעת לעצב את המהלכים ובנוסף, אנו רוצים לעקוב לאחר כל התהליכים.
הלוגיקה של ביצוע מהלך היא זהה לכל המהלכים מלבד האשכרה מהלך והלוגיקה של היא גם זהה לכל המהלכים.
בכך אנו נצור מחלקה כך:
וכך נתכנת אותו:
class GameMove
{
public:
virtual void apply(Game&) cont = 0;
virtual void undo(Game&) cont = 0;
};
class Fold : public GameMove
{
public:
void apply(Game&) const override;
void undo(Game&) const override;
};
class Call : public GameMove
{
public:
void apply(Game&) const override;
void undo(Game&) const override;
};
class Raise : public GameMove
{
int amount;
public:
void apply(Game&) const override;
void undo(Game&) const override;
};
class Game
{
vector<GameMove*> moves;
public:
void step()
{
currentPlayer->makeMove(*this);
}
void makeMove(GameMove* move)
{
cout << "Applied: " << move;
moves.push_back(move);
move->apply(*this);
currentPlayer = nextPlayer();
}
void undo()
{
int n = moves.size();
if (n > 0)
{
currentPlayer = prevPlayer();
moves[n - 1]->undo(*this);
moves.pop_back();
}
}
};
אם נשלב ו- נקבל את ה- הבא:

ודיאגרמת הרצף של היצור הזה יראה כך:

תבניות עיצוב מבניות
תבנית עיצוב
נניח והיינו רוצים להוסיף למשחק שלנו ונניח ויש ספריה שמישהו יצר עם מושלם, והיא ממומשת כך:
namespace GuiLib
{
typedef int Suite;
typedef int Rank;
struct Card;
class Cards;
class Agent;
class CardGame;
class GuiWindow;
}
זה לא אותן מחלקות כמו שאנחנו יצרנו, ואנו רוצים להשתמש במה שאנחנו יצרנו כי כנראה עיצבנו את זה בדרך שהיא מסויימת וטובה למה שאנחנו עושים.
בנוסף, אם אי פעם נרצה לעבוד עם עוד ספריה, לא נוכל לעבוד לפי המחלקות האלו וגם המחלקות של הספריה האחרת ולכן הכי עדיף לעבוד עם מה שאנחנו יצרנו.
בזה בדיוק מטפל והוא מאפשר לנו לבצע טרנספורמציה על מבנים שיצרנו למבנים שיצרו אחרים.
הרעיון הבסיסי הינו:

ואצלנו במשחק זה יהיה:
וכך זה ימומש בקוד:
namespace Poker
{
class PlayerAdapter : public GuiLib::Agent
{
Player* m_player;
public:
PlayerAdapter(Player* player) : m_player(player) {}
string getName() const override
{
return m_player->getName();
}
GuiLib::Cards getCards() const override
{
return convertHandToCards(m_player->getHand());
}
}
}
תבנית עיצוב
נניח כעת כי ויתרנו על שימוש בספרייה ואנו רוצים לממש בעצמנו את ה-.
בנוסף, נרצה להוסיף כמה פיצרים מעניינים:
- תמונת פרופיל לכל שחקן
- אנימציות
- סטטיסטיקות
איך נוכל להוסיף את הדברים האלו בתצורה טובה?
- הפתרון הבנאלי הוא לבצע ירושה, אבל אנו לא רוצים לעשות את זה כי זה לא תומך בכמה פיצרים שונים.
- הפתרון האמיתי הוא
נסתכל על איך זה נראה:

יש גם ירושה וגם שדה של מחלקת האב וכך זה נראה בקוד:
class GuiWindow
{
public:
virtual void display(Game& game);
};
class AnimatingWindow : public GuiWindow
{
GuiWindow* window;
public:
AnimatingWindow(GuiWindow* window) : window(window) {}
void display(Game& game) override
{
window->display(game);
animate(game);
}
};
ההתנהגות של זה נראה כך:
GuiWindow* animatingWindowWithStatistics = new StatisticsWindow(new AnimatingWindow(new GuiWindow()));
animatingWindowWithStatistics->display(game);

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

בקוד זה יראה כך:
class MessageFactory
{
bool m_isSecurityNeeded = false;
public:
MessageFactory(bool isNeeded) : m_isSecurityNeeded(isNeeded) {}
Message* create() const
{
if (!m_isSecturityNeeded)
return new Message();
else
return new CryptoMessage();
}
};
class Game
{
MessageFactory* m_factory;
public:
Game(MessageFactory* factory) : m_factory(factory) {}
void sendMove(const GameMove& move) const
{
Message *message = m_factorty->create();
message->sendMessage(move.description());
}
};
ניתן כמובן לעשות גם מפעל אבסטרקטי וזה עדיף בהרבה מלא אבסטרקטי:

תבנית עיצוב
לפעמים נרצה להבטיח כי למחלקה מסויימת יהיה רק מופע אחד. (נניח מחלקה של או מחלקה של מסך ראשי)
הבעיה ש- בה לפתור היא איך אנו מבטיחים שיש רק משתנה אחד ואיך נוכל לגשת אליו.
מימוש אחד אפשרי הינו משהו כזה:
class Game
{
Game() {}
public:
static Game& getInstance()
{
static Game instance;
return instance;
}
Game(const Game& other) = delete;
Game& operator=(const Game& other) = delete;
};
נשתמש בזה כך:
Game::getInstance().step();