Upload
others
View
7
Download
2
Embed Size (px)
Citation preview
Design Patterns 4
Design Patternsמאיר סלע
מהדורה ראשונה 2004
הדפסה 1
כל הזכויות שמורות
מרכז ההדרכה עיט� 2000
www.mh2000.co.il :אתר אינטרנט
[email protected] :דואר אלקטרוני
אי� להעתיק, לשכפל או לצל� ספר זה או קטעי� ממנו, בשו� צורה ובשו� אמצעי אלקטרוני,
אופטי או מכני לכל מטרה שהיא, ללא אישור בכתב מההוצאה לאור.
פרק 1 : מבוא 13
1 . מבוא
נושאי הפרק:
• Design Patterns � הגדרה, מאפייני, יישו
• מדדי בפיתוח תוכנה � וכיצד Design Patterns מסייעי בהשגת
• Reference � מקורות ספרות
Design Patterns 14
Design Patterns
design patterns הוא תחו� במסגרת הכללית של תיכו מונחה עצמי� (Object Oriented Design), הכולל טכניקות תיכו� ותכנות מתקדמות.
המקור התיעודי העיקרי ל� patterns הוא הספר[Gamma95]. לספר ארבעה מחברי�, ולכ� ה�
מכוני� לפעמי� בקיצור ג� Gang Of Four � GOF. מחברי הספר עצמ� מעידי� שמקור
ההשראה העיקרי עבור� היה ספרו של כריסטופר אלכסנדר [Alexander79] שעסק בתכנו�
וארכיטקטורה אורבניי�.
בספר זה נסקור את ה� patterns התקניי� והמוכרי� בתעשיית התוכנה. לכל pattern נית� ש�
מזהה ותיאור בשלושה חלקי�:
− תיאור הקשר (context) הבעיה תו� התבוננות בדוגמא
.C++/Java/C# כמו ג� קוד דוגמא ב� ,UML תיאור הפתרו� בצורת תרשי� תיכו� −
− הכללת הפתרו� למקרה הכללי תו� ציו� קשרי� ע� patterns אחרי�, וריאציות שונות
של הפתרו�, השלכות, ושימושי� לדוגמא.
כפי שנאמר, המקור העיקרי ל� patterns הוא הספר [Gamma95]. עקב גילו, שפת התרשימי�
שבו אינה עדכנית (לא UML), מערכות העצמי� ודוגמאות הקוד שבו מיושנות. ספר זה עושה
שימוש בתרשימי UML, כולל עדכוני� רבי�, תוספות ודוגמאות ממערכות עצמי� מודרניות.
Design Pattern � מה זה?
קיימות מספר הגדרות ל� design pattern, אול� הדר� הבהירה ביותר להבנתו היא באמצעות
מאפייניו. pattern מצוי :
• מספק פתרו� לבעיה כללית החוזרת ומופיעה בצורות שונות
• יכול לכלול מספר וריאציות של הפתרו� (בהתא� לוריאציות של הבעיה ולשיקולי תיכנו�
נוספי�)
• נבדק ונוסה כבר פעמי� רבות (בהצלחה)
• בד"כ מתייחס לתחושת dejavu של מפתחי� מנוסי� בהקשר הנתו�
• לרוב ממומש ע"י שיתו� פעולה בי� מספר מחלקות/עצמי�
• לרוב הוא אינו המצאה או גילוי של אד� מסויי�
פרק 1 : מבוא 15
:GOF הגדרה של מחברי הספר
Design Patterns - “Descriptions of communicating objects and classes that arecustomized to solve a general design problem in a particular context.”
: [Alexander79] הגדרה של אלכסנדר בספרו
“Each pattern is a three-part rule, which expresses a relation between a certaincontext, a problem, and a solution.“
patterns מטרות בלימוד ה�
המטרות העיקריות בלימוד ה� patterns שבספר זה ה�:
• הכרה וזיהוי של תבניות בעיות ופתרונ� � בכדי לזהות pattern ראשית יש להכירו, ולהפני�
את המבנה שלו, כמו ג� את שמו. לא תמיד קל לזהות patterns, אול� הזמ� והתרגול
מסייעי� בכ�.
patterns רכישת שפה "גבוהה" לביטוי ולתיעוד התבניות הנ"ל � לאחר תהלי� ההפנמה של ה� •
נית� לקטו� פירות נוספי�: קיבלנו שפת patterns שנית� להשתמש בה במסגרת תיכו�, ייעו�,
שיחה, תיעוד ולימוד.
לדוגמא, במקו� לכתוב בתיעוד מחלקה מסויימת בתכנית שהגדרת את ה� constructor כ�
protected בכדי למנוע ייצור מפורש של עצמי� מהמחלקה, א� סיפקת פונקציה סטטית לקבלת המופע היחיד שלו, אתה יכול להשתמש במילה אחת השקולה לתיאור הנ"ל:
.Singleton
• לימוד מניסיונ� של אחרי� בנושאי תיכו� ותכנות � ה� patterns ה� למעשה מאגר של הידוע
עד כה בנושאי תכנות מונחה עצמי�. מאגר זה נאס� ונבדק במש� שני� על סמ� נסיונ� של
רבי� וטובי�, והצגת הידע הזה כקטלוג של טכניקות תכנות מקצרת את תהלי� הלימוד של
.OO ה"תורה שבעל�פה" עבור מהנדסי תוכנה פחות מנוסי� ב�
תוצרי לוואי:
• לימוד ותרגול UML כשפה לתיאור מודלי תוכנה � מרבית הדיוני� שבספר זה מתבצעי�
ברמת התיכו� ב� UML, כאשר פה וש� מובאות דוגמאות קוד. אחת התוצאות של לימוד ה�
patterns היא הכרת UML בצורה מחודדת ומעמיקה.
patterns התנסות בתיכו� ובמעבר מתיכו� למימוש � שיקולי התיכו� שמלווי� חלק גדול מה� •
מספקי� התנסות עשירה בבחינת השפעת� במעבר למימוש.
Design Patterns 16
• הכרה והתנסות ע� מנגנוני� מתקדמי� ב� OOP: פולימורפיז� ופולימורפיז� טהור,
.RTTI ,templates / Generics
C# ,Java ,C++ בספר מובאות דוגמאות בשפות �חשיפה למערכות עצמי� מתקדמות •
ובמערכות MFC, .NET, Java, COM. ה� patterns עוזרי� להבי� בקלות ובמהירות מערכות
וספריות מורכבות מאוד.
מדדי בפיתוח תוכנה
המטרה המשותפת לכל ה� patterns שבספר זה היא פיתוח תוכנה "טובה" יותר. השאלה
הראשונה שעולה מכא� היא "מהי תוכנה טובה יותר?" � לש� כ� קיימי� מספר מדדי� מקובלי�
להערכת תוכנה:
• גמישות � היכולת לבצע שינויי� בתוכנה ללא מאמ� רב.
• הרחבה � היכולת להרחיב את התוכנה (בדר� כלל פרוייקטי תוכנה מתרחבי� ע� הזמ�).
• יעילות � ניצול משאבי מקו� וזמ� שצורכי� מרכיבי המערכת.
• בהירות � היכולת ללמוד את המערכת ללא מאמ� רב ובזמ� קצר.
• ניידות � יכולת הסבה של התוכנה לספריות, מערכות ומחשבי� שוני�.
במילי� אחרות, תוכנה היא טובה יותר ככל שהיא יעילה יותר, גמישה יותר בפני ביצוע שינויי�,
מאפשרת תוספות בצורה קלה ופשוטה יותר וככל שנית� להסב אותה לסביבות שונות ביתר
קלות.
מדד אחר שמקובל להגדיר באופ� כללי הוא פשטות, והוא כולל בתוכו את כל המדדי� האחרי�:
ככל שהמערכת פשוטה וטבעית יותר, היא מובנת יותר, נוחה יותר להרחבה ולשינוי ולכ� טובה
."Keep It Simple" לביטוי KIS יותר. באנגלית קיי� קיצור
מדדי� פרטניי� יותר
השאלה: כיצד בוני� תוכנה שתענה לדרישות הנ"ל � יעילה, בעלת יכולת גמישות, יכולת הרחבה
ויכולת נייוד טובות יותר?בכדי לענות לשאלה, עלינו לפרוט את המדדי� הנ"ל למדדי משנה:
• מודולריות � מערכת מודולרית היא מערכת המחולקת למרכיבי תוכנה, באופ� היררכי, כ�
שכל נושא בה מטופל ע"י מרכיב אחד. מדד זה נקבע בשלבי התיכו� המוקדמי� של המערכת:
בתהלי� ה� decomposition מכריעי� כיצד לפרק את המורכבות של המערכת לכדי מודולי�,
כ� שהצימוד (להל�) ביניה� יהיה קט� ככל האפשר. כמו כ�, תכנו� מודולרי של המערכת
מאפשר שימוש חוזר (Reusability) במרכיביה ביתר קלות.
פרק 1 : מבוא 17
• צימוד Decoupling / (Coupling) � אלו ה� שני הפכי�: בתכנו� מערכת אנו מנסי� להשיג
decoupling מקסימלי בי� רכיבי תוכנה שוני�. בדר� כלל, ככל שהצימוד קט� יותר קל יותר לבצע שינויי� והרחבות. פולימורפיז� הוא מנגנו� יעיל מאוד לפיתוח רכיבי תוכנה ע� צימוד
מינימלי. כמעט כל ה� patterns מקטיני� את הצימוד שבי� מרכיבי� שוני� ה� ע"י שמוש
.(Forwarding) בפולימורפיז� וה� ע"י שימוש בהפנייה
לדוגמא, ה� Command (עמוד 185) מקטי� את הצימוד שבי� יוז� הפקודה לבי� העצ�
המקבל ומבצע אותה. יתר על כ�, היוז� עצמו אינו מכיר ולכ� ג� לא תלוי בסוגי ה�
Commands הקיימי� במערכת, בזכות המבנה הפולימורפי שלה�. מה שאומר ששינוי כלשהו במבצעי הפקודות, או הוספת Commands חדשי� למערכת, אינ� מצריכי� הידור מחודש
שלו.
ה� Proxy (עמוד 105) מקטי� את הצימוד שבי� קוד הלקוח לבי� עצ� נתו� ע"י חציצה בי�
השניי� וביצוע הפנייה (Forwarding) של קריאות הלקוח לעצ� המטרה.
• ניצול מקו טוב יותר � בתכנו� נכו� יותר של המערכת נית� לצרו� פחות מקו� בזכרו�
התכנית, ה� זה שבשימוש ע"י הקוד וה� זה שבשימוש ע"י הנתוני�. ה� Proxy (עמוד 105) וה�
Flyweight (עמוד 162) מספקי� ניצול טוב יותר של זכרו� התכנית ע"י שימוש בשיתו�.
• ניצול זמ� טוב יותר � הדר� שבה מבוצעות פעולות במערכת יכולה להשפיע בסדרי גודל על
ביצועי הזמ� שלה. לדוגמא, שימוש בפולימורפיז� בתכנו� OO הוא יעיל יותר משימוש
במשפטי תנאי כגו�: switch-case � קריאה לפונקציה וירטואלית מתבצעת ב� O(1), בעוד ש�
switch-case בוחר את הכניסה המתאימה ב� O(n), כאשר n הוא מספר ה� �caseי� שבמשפט.
מגוו� של patterns התנהגותיי� מייעלי� את זמ� הביצוע של משימות באופ� הנ"ל:
Command (עמוד 185), State (עמוד 199), Visitor (עמוד 232), Prototype (עמוד 60), Prototype-based Factory (עמוד 64)
פרמטר זמ� נוס� הוא מש� הפיתוח של המערכת: ג� כא�, נית� בבירור לומר שתוכנה
מודולרית ופשוטה יותר תהיה בעלת מש� פיתוח קצר יותר מזה של מערכת פחות מודולרית.
• תלות (Dependency) / אי�תלות � עפ"י דרישות היישו�, אנו משתדלי� להבי� אילו תלויות
עלולות להכביד על ביצוע שינויי� והרחבות במערכת כבר בשלב התכנו�. בתכנו� שלוקח
בחשבו� שינויי� עתידיי� (”Design for Change“) מקטיני� למינימו� את התלויות
במרכיבי� העשויי� להשתנות.
לדוגמא, א� אלגורית� המוגדר ע"י פונקציה במחלקת תוכנה כלשהי עשוי להשתנות, וא�
מעבר לכ� האפשרות להחליפו באלגוריתמי� אחרי� באופ� דינמי עשויה לקד� את הפיתוח,
רצוי לשקול שימוש ב� Strategy (עמוד 245).
• יכולות דינמיות � פה אנו מדברי� על האפשרות לבצע שינויי� ותוספות בזמ� ריצה. לדוגמא,
תכנו� מסויי� של יישו� הכולל תפריט עשוי לטפל בכל האפשרויות הקיימות בתפריט בזמ�
קומפילציה. אול�, הא� נית� להוסי� פריטי� או להסיר פריטי� מהתפריט בזמ� ריצה?
יכולת דינמית כזו חוסכת את הצור� בקידוד ובהידור מחודש בכל שינוי בתפריט. ה�
Design Patterns 18
Command (עמוד 185) מתאר דר� יעילה למענה לדרישה זו.
באופ� דומה, State (עמוד 199) מספק יכולת דינמית בקביעת מצב העצ�, כמו ג� להוסי�
ולבטל מצבי� בזמ� ריצה. Strategy (עמוד 245) מספק יכולת דינמית בקביעת סוג
האלגורית� שיופעל בקריאה לפונקציה מסויימת של עצ� נתו�.
patterns מיפוי דרישות ספציפיות ל�
design patterns מיועדי� למת� מענה לדרישות מסויימות, בהקשר נתו�. בסעי� זה נראה מיפוי של דרישות ספציפיות ל� patterns המתאימי�:
• אי�תלות באופ� הייצוג והמימוש של העצ� � לפי דרישה זו קוד הלקוח לא צרי� להיות מודע
לפרטי הקוד של המחלקה.
Adapter (156), Bridge (166), Facade (133), Proxy (105), Memento (224)
• אי�תלות באופ� יצירת העצ� � קוד הלקוח לא צרי� להיות מודע לאופ� יצירת העצמי�.
במקרי� רבי�, דרישה זו היא תוצאה ישירה של הדרישה הקודמת.
Prototype-based Factory (64), Factory Method (73), Abstract Factory (72),
Builder (77), Proxy (105)
• אי�תלות באלגורית� � נדרשת יכולת הרחבה או שינוי של אלגורית� מסויי� בעצ� במהל�
הפיתוח. כמו כ� לעיתי� אלגורית� מסויי� של העצ� צרי� להקבע באופ� דינמי.
Strategy (245), Template Method (240), Visitor (232), Iterator (209)
• אי�תלות בי� אופ� ייצוג נתוני� לבי� הצגת� � הפרדה בי� מודל הנתוני� של היישו� לבי�
ההצגה הויזואלית שלה� מפשטת את תהלי� הפיתוח.
MVC (180)
פרק 1 : מבוא 19
Reference
The Timeless Way of Building, Christopher Alexander,Oxford University Press, 1979.
[Alexander79]
Object Oriented Analysis and Design with Applications,Grady Booch ,Benjamin Cummings, 1997
הספר כולל טכניקות תיכו� מונחה עצמי� כלליות, ובכ� הוא משמש
.[Gamma 1994] כמשלי� ל�
[Booch97]
The Unified Modeling Language User Guide, Grady Booch,James Rumbaugh, Ivar Jacobson, Addison Wesley, 1999
ספר מקי� ללימוד UML, מאת האבות היוצרי� של תק� זה. החומר
מובא בצורה שוטפת וברורה, ע� תרשימי� רבי� ודוגמאות ממערכות
עדכניות.
[Booch99]
Design Patterns: Elements of Reusable Object-OrientedSoftware, Erich Gamma, Richard Helm, Ralph Johnson, andJohn Vlissides. (GOF), Addison Wesley 1995.
המקור הרשמי לנושאי Design Patterns. התוכ� ברור ומובא בצורה
נוחה לקריאה. עקב גילו של הספר, מוסכמות הסימוני� והדוגמאות
המובאות בו לא עדכניות.
[Gamma95]
Effective C++, 2nd Edition, Scott Meyers, Addison Wesley,1998
[Meyers98]
More Effective C++, Scott Meyers, Addison Wesley, 1996[Meyers96]
מדרי� מקצועי, מאיר סלע, מרכז ההדרכה עיט� (2000), שנת C++ 2002
ספר מקי� ומקצועי בנושא שפת ++C בפרט ותיכו� מונחה עצמי� בכלל.
ספר זה שבהוצאתנו כולל תיאור של כלל מרכיבי שפת ++C העדכנית.
[MH-C++02]
The design and evolution of C++, Bjarne Stroustrup , AddisonWesley, 1994
למרות גילו של הספר, הוא עדיי� משמש כאחד המקורות המרהיבי�
להבנת מבנה שפת ++C. המחבר, שהוא ג� יוצר השפה, מביא את
השיקולי� השוני� שעמדו בבחירת מנגנוני� מסוימי� לשפה ובדחיית
אחרי�, ובכ� מרחיב את הבנת הקורא ב� ++C בפרט ובתוכנה בכלל.
[Strous94]
Design Patterns 20
The C++ Programming Language, 3rd Edition, BjarneStroustrup , Addison Wesley, 1997
ספר התנ"� לשפת התכנות ++C, מאת יוצר השפה. הספר מקי� מאוד
ומעמיק מאוד באופ� בלתי מתפשר, לעיתי� א� על חשבו� הדידקטיות.
התוכ� מצטיי� בניסוח מדויק וזהיר.
[Strous97]
Pattern Hatching, Design Patterns Applied, John Vlissides,Addison Wesley 1998.
[Vlissid98]
פרק patterns : 3 בייצור עצמי� 51
patterns . 3 בייצור עצמי�
ה� patterns שבפרק זה עוסקי בייצור של עצמי, עבור מקרי בה משימה זו אינה
טריוויאלית, או שצריכה להתבצע תחת מגבלות מסוימות.
• Singleton � הגבלת מספר העצמי המיוצרי ממחלקה מסוימת בתכנית ל� 1, ומת� גישה
גלובלית לעצ היחיד.
• Prototype � ייצור פולימורפי של עצ עפ"י אבטיפוס (prototype) נתו� ע"י שיבוט.
• Prototype-based Factory � ייצור פולימורפי של עצ עפ"י מפתח, תו� שימוש ב�
.Prototype
כמו כ� נסקור בקצרה מספר patterns סטנדרטיי נוספי:
• Abstract Factory � ממשק לייצור משפחות עצמי ללא ציו� הטיפוס הקונקרטי שלה.
• Factory Method � פונקציה לייצור עצ המוגדרת במחלקת הבסיס, כשהמחלקות הנגזרות
מחליטות איזה עצ בדיוק לייצר.
• Builder � הפרדת הייצור של עצ מורכב מהייצוג שלו, כ� שאותו תהלי� ייצור יאפשר לבנות
ייצוגי שוני של העצ.
כסיכו לסעי� זה נראה כיצד נית� לממש Serialization במערכת מונחית עצמי תו� שימוש ב�
patterns הנ"ל.
Design Patterns 52
Singleton
בעיה
נתונה מחלקת מערכת System המספקת שירותי מערכת שוני�: שירותי שעו, שירותי ניהול
זיכרו, טעינת ספריות דינמיות (DLL), וכ קבלת מידע על תכונות המערכת הנוכחית:
+System()+time_service()+mem_service()+load_library()+get_property()
-properties
System
קוד המחלקה:
// system.h#include <string>#include <map>using namespace std;
class System{public:
System() { properties["os"]="Windows"; properties["lang"]="Heb";}void time_service() { /*...*/ }void mem_service() { /*...*/ }void load_library (string lib) { /*...*/ }string get_property(string name) const { returnproperties[name];}
private:map<string, string> properties;
};
דרישות
אנו מעוניני� שמ� System יהיה מופע אחד לכל היותר בתכנית. כמו כ, אנחנו מעוניני� לספק
גישה גלובלית לעצ� היחיד של המחלקה. יש לשי� לב לדרישות מהפתרו:
• לכל היותר עצ� אחד מהמחלקה System יהיה בתכנית. משמעות הדבר, שייתכ מצב בו לא
יהיה ג� עצ� אחד � במידה ולא נעשה שימוש בו בריצת התכנית.
פרק patterns : 3 בייצור עצמי� 53
• הפתרו� צרי� להיות כפוי על כל קוד התכנית � כלומר, יש להבטיח שלא ייווצר יותר מעצ�
יחיד בתכנית א� לא כתוצאה משגיאה של מתכנת/ת.
• יש לאפשר גישה לעצ� היחיד מכל מקו� בתכנית, כלומר, הגישה לעצ� צריכה להיות
גלובלית.
פתרו� שגוי
:.h פתרו� 1: הגדרת העצ� כגלובלי. נכריז על העצ� בקוב� ה�
system.h:#include <string>#include <map>using namespace std;
class System{...};
extern System g_system; // declaration of global system object
ונגדיר אותו בקוב� המימוש:
system.cpp:#include “system.h”
System g_system; // definition of global system object
קוד המשתמש יפעיל את השירותי� של g_system, למשל כ�:
g_system.load_library(“my_lib.dll”);
מה לא בסדר בפתרו� זה?
− אי� מניעה של אפשרות ייצור עצמי� נוספי� מהמחלקה. עמידות הפתרו� נתונה
לחסדיו של קוד המשתמש � היא אינה נכפית עליו ע"י המהדר.
− בדרישות הבעיה צויי� שיש לאפשר ייצור עצ� אחד לכל היותר, ולא לייצר אותו א�
לא נעשה בו שימוש. כא� אנו מקצי� את העצ� תמיד.
− "זיהו�" מרחב השמות הגלובלי � המשתנה g_system נוס� למרחב השמות הגלובלי.
במערכות גדולות מאוד, קיימת הסתברות להתנגשות ע� שמות זהי� במרחב הגלובלי,
ונדרשות מוסכמות במת� שמות בכדי למנוע זאת. לדוגמא, נהוג להוסי� לשמות
במרחב השמות הגלובלי 3 אותיות המציינות את ש� תת�המערכת לה משתיי� הש�.
Design Patterns 54
− בעיה חמורה במיוחד עלולה לצו� באיתחול המערכת: נניח שבמערכת מספר עצמי
Singleton כנ"ל � DB, Logger, Clock, System � ובאיתחול שלה� (ב� constructor) ה� תלויי� הדדית עפ"י הגר� הבא:
Clock
System
Logger
DB
לדוגמא, ח� מ� DB ל� Logger מציי� שב� constructor של המחלקה DB עושי� שימוש בעצ�
.DB להיווצר לפני יצירת Logger לכ� על העצ� .Logger ה�
אול�, מכיוו� שב ++C/C לא מוגדר סדר על יצירת העצמי� הגלובליי� (המוגדרי� בקבצי�
שוני�), מתעוררת פה בעיה איתחול חמורה. הדרכי� להתמודדות ע� בעיה זו ה� להגדיר את כל
העצמי� הגלובליי� בקוב� מימוש אחד (ואז סדר היצירה הוא עפ"י סדר הגדרת�), או לבצע 2
.(double phase initialization) פאזות של איתחול
פתרו� 2: הגדרת כל ה� members כסטטיי�:
system.h:#include <string>#include <map>using namespace std;
class System{public:
static void time_service() { /*...*/ }static void mem_service() { /*...*/ }static void load_library (string lib) { /*...*/ }static string get_property(string name) const
{ return properties[name];}private:
פרק patterns : 3 בייצור עצמי� 55
static map<string, string> properties;};
ובקוב� המימוש יש להגדיר את המשתני� הסטטיי� של המחלקה:
system.cpp:#include “system.h”
map<string, string> System::properties;
בפתרו� זה � לעומת הקוד� � מובטח לנו שלא יהיה נית� לייצר יותר מהעתק אחד של ה�
members של המחלקה (ייתכ� ומספר מתכנתי� ייצרו מספר מופעי� ל� System, א� ה� עדיי� יחלקו את אות� members, עקב היות� סטטיי�).
כמו כ�, מרחב השמות הגלובלי לא מזוה�, מכיוו� שאי� צור� בייצור עצ� מהמחלקה � השירותי�
שבה מופעלי� בהקשר למחלקה, למשל כ�:
System::load_library(“my_lib.dll”);
מה לא תקי� בפתרו� זה?
− כמו בפתרו� הקוד�, ג� כא� ה� members מוקצי� תמיד, ג� א� לא נעשה בה� כל
שימוש בריצת היישו�.
− כמו בפתרו� הקוד�, סדר היצירה של עצמי� סטטיי�, כמו גלובליי�, לא מוגדר ולכ�
הבעיה שרירה וקיימת.
פתרו�
המחלקה System תוגדר כ� Singleton. היא תכלול:
− פונקציה סטטית בש� ()instance המספקת עצ� מהמחלקה. עצ� זה יוגדר כסטטי
בפונקציה.
− ה� constructor יוגדר כ� protected בכדי למנוע ייצור עצמי� ע"י קוד חיצוני. מאותה
סיבה ג� ה� copy constructor יוכרז כ� protected (הוא לא מוגדר כ� private בכדי
.(Singleton לאפשר ירושה מה�
Design Patterns 56
-System()+time_service()+mem_service()+load_library()+get_property()+instance()
-properties
System
:C++ קוד המחלקה ב�
// system.h#include <string>#include <map>
using namespace std;
class System{public:
void time_service() { /*...*/ }void mem_service() { /*...*/ }void load_library (string lib) { /*...*/ }string get_property(string name) const { returnproperties[name]; }
// global point of access to the sole instancestatic System& instance(){
static System object;return object;
}protected:
System() { properties["os"]="Windows"; properties["lang"]="Heb";}System(const System&);
private:map<string, string> properties;
};
יש לשי� לב שה� copy constructor כלל אינו נדרש להגדרה � אול� מכיוו� שמסופק כזה
במחדל, מכריזי� עליו כ � protected ולא ממשי� אותו (היינו יכולי� לממש אותו כפונקציה
ריקה, וג� אז הוא גור� לשגיאת הידור בנסיו� של קוד חיצוני למחלקה לעשות בו שימוש. היתרו�
שבאי מימושו הוא שכעת תתקבל שגיאת הידור ג� בנסיו� של קוד המחלקה עצמה, או מחלקות
צאצאות, לנסות לייצר העתקי� של העצ�!)
קוד משתמש לדוגמא:
#include <iostream>#include "system.h"using namespace std;
פרק patterns : 3 בייצור עצמי� 57
void main(){
System::instance().time_service();System::instance().mem_service();System::instance().load_library("mylib.dll");cout << "Operating system: " <<System::instance().get_property("os") << endl;cout << "Language: " << System::instance().get_property("lang")<<endl;
System s1; // error: private constructorSystem s2(System::instance());// error: private copy constructor
}
הכללה
מגדירי� מחלקה כ� Singleton כאשר מעונייני� לספק מופע אחד שלה, לכל היותר, בתכנית.
הגישה למופע זה היא גלובלית עבור כל מרכיבי התכנית:
#Singleton()+operation1()+operation2()+operation3()+instance()
Singleton
− פונקציה סטטית מספקת reference לעצ� היחיד המוגדר בתוכה כסטטי
protected מוגדרי� כ� constructors ה� −
• יתרונות:
− ה� Singleton מונע "זיהו�" של מרחב השמות בכ� שעצ� המחלקה אינו מוגדר
במרחב השמות הגלובלי. בכ� למעשה ג� מסופקת גישה תקנית לעצמי� של
.instance() המשתמש תמיד ניגש לעצמי� ע"י הפונקציה :Singletons
− Singleton זמי� תמיד למשתמש בו: בניגוד לעצמי� גלובליי� שעבור� סדר הבנייה
אינו מוגדר בתכנית ++C � ולכ� אינ� יכולי� להתייחס אחד לשני בשלבי ה�
constructors שלה� � ה� Singletons נוצרי� עפ"י סדר השימוש בה�, ולכ� לא חלה עליה� מגבלת שימוש כלשהי.
Design Patterns 58
• חסרונות:
− קשה לעשות שימוש ב� Singleton בהיררכיות ירושה. כלומר, יש קושי עבור מחלקת
.Singleton לרשת ממחלקה אחרת, או לרשת ממחלקת ה� Singleton ה�
− ה� Singleton הוא יחיד פר יישו�, הואיל והוא מוגדר כסטטי בקטע הנתוני�
.Thread פר Singleton לא נית� להגדיר בטכניקה זו .(Data Segment)
• וריאציות:
− קיימת שיטה נוספת להגדרת Singleton: נית� להגדיר מצביע לעצ� היחיד כסטטי
במחלקה (static member), ולייצר אותו בפע� הראשונה שנקראת הפונקציה
הסטטית ()instance. שיטה זו היא מעט יותר מסורבלת ובעלת חסרו� משמעותי: ה�
[Vlissid98] אינו נקרא בסו� התכנית (ראה/י דיו� ב� Singleton של ה� destructorבסעי� ”To kill a Singleton“). בשפות כגו� #Java/C הבעיה לא קיימת בזכות ה�
.Garbage Collector
Java ב� Singleton מימוש
ב� Java לא קיי� מנגנו� של משתני� סטטיי��מקומיי�. לכ� אפשרות המימוש היחידה היא זו
.static-member של
:static member בשיטת ה� Singleton דוגמא למימוש
class Singleton{
private static Singleton m_instance; // the singleton object
public static Singleton instance() // static access method{
if (m_instance == null)m_instance = new Singleton();
return m_instance;}protected Singleton() {...} // protected constructor
… // rest of the class
}
• Thread Safety: במערכת מרובת Threads, עלול להיווצר מצב שבו שני Threads (או יותר)
Threads זה יכול לקרות כאשר מבוצעת החלפת .Singletonיבצעו את שורת היצירה של ה�
מיד לאחר שורת הבדיקה
if (m_instance == null)<<context switch>>m_instance = new Singleton();
נית� למנוע בעיה זו ע"י הוספת מנגנו� סינכרו� � נית� להגדיר את קטע יצירת העצ� כ�
:synchronized
public static Singleton instance() {
פרק patterns : 3 בייצור עצמי� 59
synchronized(Singleton.class) { // protect critical codeif (m_instance == null)
m_instance = new Singleton();}return m_instance;
}
יש לשי� לב לכ� שההגנה מבוצעת בהקשר המחלקה (Singleton.class) מכיוו שהפונקציה היא
סטטית, והיא נקראת בהקשר זה.
• הערות:
− א� מחלקת ה� Singleton מממשת את java.io.Serializable הרי שנית
.deserialization לייצר יותר מעצ� אחד ממנה ע"י ביצוע חוזר של
− מכיוו שה� constructor מוגדר כ� protected, יכולות מחלקות צאצאות של
Singleton או מחלקות המשתייכות לאותו ה� package לעקו� את מגבלת הייצור שלו. א� רוצי� למנוע זאת, נית להגדיר את ה� constructor כ� private, אול�
.Singleton בכ� ג� תמנע אפשרות ירושה מה�
C# ב� Singleton מימוש
הקוד ב� #C דומה לזה של Java, ע� מעט הבדל במנגנו הסינכרו ובמודל השיקו�:
class Singleton{
protected static Singleton m_instance; // the singleton object
public static Singleton instance() // static access method{
lock(typeof(Singleton)) { // protect critical codeif (m_instance == null)
m_instance = new Singleton();}return m_instance;
}protected Singleton() {/*...*/} // protected constructor
}
ג� כא אנו מבצעי� הגנה מפני גישה של יותר מ� Thread אחד ע"י מנגנו הסינכרו lock, שמג
.typeof(Singleton) המתקבל מ� (Type מסוג) Singleton על טיפוס המחלקה של
Design Patterns 60
Prototype
בעיה
נתונה היררכית מחלקות הודעות:
+set()+print()
«interface»Message
+set()+print()
-m_number-m_image
Fax
+set()+print()
-m_address-m_text
+set()+print()
-m_text
Memo
הבעיה: כיצד לייצר עצ� כהעתק של עצ� הנתו� באופ� פולימורפי.
למשל, הפונקציה הבאה מקבלת כפרמטר מצביע פולימורפי, והיא מעונינת לייצר העתק שלו:
void f(Message *pm){
Message *new_msg = ???//...
}
מכיוו� שלא ברור מהו הטיפוס המדויק של העצ� המוצבע ע"י pm אי� דר� פשוטה לייצר עצ�
דומה לו.
פרק patterns : 3 בייצור עצמי� 61
פתרו�
נוסי� פונקציה וירטואלית בש� ()clone להיררכיה של ההודעות שמחזירה העתק של העצ�:
+set()+print()+clone()
«interface»Message
+set()+print()+clone()
-m_number-m_image
Fax
+set()+print()+clone()
-m_address-m_text
+set()+print()+clone()
-m_text
Memo
הפונקציה ()clone היא למעשה מעי� copy constructor וירטואלי. היא תוגדר כוירטואלית
טהורה במחלקת הבסיס כ�:
class Message{public:
virtual ~Message() {}virtual Message * clone() const = 0;virtual void set(const string s1, const string s2) = 0;virtual void print() const = 0;
};
יש לשי� לב שהפונקציה מחזירה מצביע פולימורפי ל� Message. כ�, למשל, תדרוס המחלקה
Fax את הפונקציה:
class Fax : public Message{public:
Fax() : m_number(0) {}Fax(const string num, const string image) :m_number(atol(num.c_str())), m_image(image){}virtual Message* clone() const { return new Fax(*this); }virtual void set(const string num, const string image){m_number=atol(num.c_str()); m_image=image;}virtual void print() const { cout << " * Fax: num=" << m_number<< " Image=" << m_image << "\n";}
private:long m_number;string m_image;
};
וכ� תמומש הפונקציה ()f שלעיל:
Design Patterns 62
void f(Message *pm){
Message *new_msg = pm->clone();// ...
}
הכללה
Prototype (הנקרא ג� ”Virtual Constructor“) הוא פתרו� למצב בו צרי� לייצר עצ� בזמ� ריצה, שרק טיפוסו הפולימורפי ידוע בזמ� הידור.
תרשי� כללי:
+clone()
Prototype
+clone()
ConcretePrototype1
+operation()
Client
+clone()
ConcretePrototype2p = prototype->clone()
prototype
return copy of self return copy of self
• יתרונות:
− מודולריות: קוד הלקוח אינו תלוי בטיפוסי� המסוימי� הנגזרי� ממחלקת הבסיס �
הפונקציה ()clone תחזיר תמיד העתק של העצ� הנתו�.
− יעילות: העתקת העצ� מבוצעת באופ� מיידי, בניגוד לשאילתת טיפוס הצורכת זמ�
ומקו�.
• חסרונות:
,clone() לא נית� להעביר פרמטרי� מטיפוסי� שוני� עפ"י העצ� המיוצר לפונקציה −
הואיל והממשק שלה חייב להיות אחיד (פולימורפי). כתחלי�, יש לספק פונקציה
לקביעת מצב העצ� (()set) שתיקרא מייד לאחר העתקת העצ�.
− יש קושי בהחלטה כיצד להעתיק את העצ�: העתקה רדודה (Shallow Copy) או
העתקה עמוקה (Deep Copy). א� העצ� כולל מצביעי�, קיי� קושי בהחלטה הא�
לספק עצ� החולק את המצביעי�, או לספק עצ� ע� מידע כולל חדש.
פרק patterns : 3 בייצור עצמי� 63
בדר� כלל מופעל ה� copy constructor בפונקציה ()clone, והוא זה ש"מחליט" כיצד
להעתיק את העצ�, אול� נית� להפעיל ג� constructor מחדל, למשל.
• וריאציות : Stroustrap מציי� בספרו [Strous97] במסגרת ה� Relaxation Rules את
האפשרות (בהתא� למימוש הקומפיילר) להחזיר במחלקה הנגזרת מצביע לטיפוס שלה,
כלומר:
class Base{
...virtual Base * clone() const = 0;
};
class Derived : public Base{
...virtual Derived * clone() const { return new Derived(...); }
};
זה יאפשר שימוש לא פולימורפי בפונקציה ()Derived::clone ג� ללא צור� ב� casting. לא
הרבה קומפיילרי� תומכי� באפשרות זו כיו�.
• שימושי� ידועי�:
− Java כוללת במחלקת הבסיס שלה, Object, את השירות ()clone המחזיר מצביע
מסוג Object לעצ� הנוכחי, תו� שימוש במנגנו� השיקו�. בהיות Object בסיס לכל
.Java לכל עצמי Prototype המחלקות, בכ� מסופק ממשק
יש לשי� לב שהמחלקות הנגזרות אינ� צריכות לדרוס את הפונקציה ()clone � גירסת
(Reflection מבצעת את כל העבודה תו� שימוש במודל השיקו� Object הבסיס
(Model. מחלקת Java צריכה לממש את הממשק Cloneable בכדי לציי� שהיא מעוניינת לספק שירות cloning (הממשק אינו כולל פונקציה כלשהי).
− באופ� דומה ל� Java, ג� ב� #C קיימת תמיכה ב� cloning כבר במחלקת הבסיס,
System.Object. אול� כא� יש הבחנה בי� העתקה רדודה (Shalow Copy) לבי� :(Deep Copy) העתקה עמוקה
MemberwiseClone() .1 מבצעת העתקה רדודה, כאשר המצביעי� בעצ�
החדש בעלי ער� זהה לאלו שבעצ� המקור.
Clone() .2 : א� עצ� מסויי� מעוניי� לאפשר העתקה עמוקה שלו, עליו לממש את הממשק IClonable תו� מימוש הפונקציה ()Clone שלו.
Design Patterns 64
Prototype-based Factory
בעיה
בהמש� ליישו� ההודעות מהסעי� הקוד�, נדרש להוסי� לתכנית מחלקת dialog לבחירת הודעה
חדשה:
+set()+print()+clone()
«interface»Message
+set()+print()+clone()
-m_number-m_image
Fax
+set()+print()+clone()
-m_address-m_text
+set()+print()+clone()
-m_text
Memo
+add_button()+get_selected()+show()+run()
NewMessageDialog
המחלקה NewMessageDialog מייצגת תיבת dialog לבחירת סוג הודעה חדשה. המחלקה
כוללת פונקציה בש� ()show לקבלת הבחירה של המשתמש � זה יכול להיות אינדקס או מחרוזת
המתארת את טיפוס ההודעה.
פרק patterns : 3 בייצור עצמי� 65
תיבת ה� dialog מציגה למשתמש שלושה כפתורי� לבחירת סוג הודעה חדשה שברצונו לשלוח:
New Message
Fax
Memo
Choose type ofmessage
שאלה: כיצד לטפל בבחירת המשתמש ובייצור ההודעה החדשה?
פתרו� שגוי
נבצע ()switch על אינדקס ההודעה המוחזר מהפונקציה ()show, ועל פי התוצאה נבצע את הקוד
המתאי�:
#include "message.h"
class NewMessageDialog{
...int show(); // display dialog and return selected indexMessage * run(){
Message *pm;int i = show(); // get message type indexswitch(i){
case 0: // Faxpm = new Fax;break;
case 1: // Mailpm = new Mail;break;
case 2: // Memopm = new Memo;break;
}return pm;
}};
Design Patterns 66
קוד משתמש לדוגמא:
int main(){
NewMessageDialog dialog;Message * p_new_msg = dialog.run();
p_new_msg->print();
// use the new message// ...
delete p_new_msg;
return 0;}
לפתרו� ע"י משפט switch שלושה חסרונות:
• הוא אינו יעיל � בכל בחירת משתמש יש לעבור על כל האפשרויות, ולבחור את הנכונה.
במקרה זה אמנ� מעט אפשרויות (3), א� במקרה הכללי, כאשר יש n אפשרויות, בממוצע
.O(n) אפשרויות, כלומר בסיבוכיות n/2 יבוצע חיפוש על
• הוא אינו מודולרי � קוד הלקוח תלוי בטיפוסי ההודעות הקיימי� במערכת וצרי� להתעדכ�
בכל שינוי או תוספת של הודעה חדשה. בנוס�, עלולה להיווצר שגיאה בהתאמת האינדקס
הנבחר לסוגי ההודעות.
• הוא אינו דינמי � לא נית� להוסי� או לבטל אפשרויות נוספות בזמ� ריצה.
פרק patterns : 3 בייצור עצמי� 67
פתרו�
הפתרו� כולל שני שלבי� עקרוניי�:
1. שימוש ב� Prototype � נעשה שימוש בפונקציה הוירטואלית ()clone שמחזירה העתק של
העצ�. כמו כ�, נוסי� פונקציה וירטואלית נוספת בש� ()get_type המחזירה את ש� הטיפוס של
ההודעה:
+set()+print()+clone()+get_type()
«interface»Message
+set()+print()+clone()+get_tpe()
-m_number-m_image
Fax
+set()+print()+clone()+get_type()
-m_address-m_text
+set()+print()+clone()+get_type()
-m_text
Memo
שתי הפונקציות מוגדרות כוירטואליות טהורות במחלקת הבסיס כ�:
class Message{public:
virtual ~Message() {}virtual Message* clone() const = 0;virtual string get_type() const = 0;virtual void set(const string s1, const string s2) = 0;virtual void print() const = 0;
};
כ�, למשל, תדרוס המחלקה Mail את הפונקציות:
class Mail : public Message{public:
Mail() {}Mail(const string addr, const string text) : m_address(addr),m_text(text) {}virtual Message* clone() const { return new Mail(*this); }virtual string get_type() const { return "Mail";}virtual void set(const string addr, const string text)
Design Patterns 68
{m_address=addr; m_text=text;}virtual void print() const { cout << " * Mail: address=" <<m_address << ", text=" << m_text << "\n";}
private:string m_address;string m_text;
};
� Message מצביעי� לעצמי � Prototypes שתחזיק טבלה של Factory 2. נגדיר מחלקה בש�
עפ"י מחרוזות המתארות את סוג ההודעה. מחלקה זו תוגדר כ� Singleton, וה� dialog יפנה
אליה לייצור עצ� עפ"י מחרוזת:
+set()+print()+clone()+get_type()
Message
+set()+print()+clone()+get_tpe()
-m_number-m_image
Fax
+set()+print()+clone()+get_type()
-m_address-m_text
+set()+print()+clone()+get_type()
-m_text
Memo
+add_button()+get_selected()+show()+run()
NewMessageDialog
+create_object()+add_proto()
-map<string, Message*> m_prototypes
Factory
m_prototypes
1*
:Factory קוד המחלקה
#include <map>#include <string>using namespace std;#include "Message.h"// Factory class - Creates Message Objects by their type name.// - Singletonclass Factory{public:
void add(const Message *m) {m_prototypes[m->get_type()] = m;}Message* create_object(const string &type)
{ return m_prototypes[type]->clone(); }static Factory& instance() {// singleton access method
static Factory factory;return factory;
}protected:
פרק patterns : 3 בייצור עצמי� 69
Factory() {}Factory(const Factory &);~Factory() { for(map<const string, const Message *>::iterator i=
m_prototypes.begin(); i != m_prototypes.end(); i++)delete i->second;
}private:
map<const string, const Message *> m_prototypes;};
(cloning) תציג את ההודעות ובבחירת המשתמש בכפתור מסוי�, יבוצע שיבוט dialog תיבת ה�
של העצ� המתאי� בטבלה:
class NewMessageDialog{public:
void add_button(string name) {}string show();Message * run(){
string sel = show();return Factory::instance().create_object(sel);
}};
:Factory פונקציה לרישו� טיפוסי ההודעות ב�
void register_prototypes(){
Factory::instance().add_proto("Fax", new Fax);Factory::instance().add_proto("Mail", new Mail);Factory::instance().add_proto("Memo", new Memo);
}
פונקציה זו יכולה להיות גלובלית או במסגרת ה� Facade (להל�, פרק 4), לדוגמא.
קוד תכנית הלקוח:
int main() {register_prototypes();NewMessageDialog dialog;Message * p_new_msg = dialog.run();
// use the new message...
delete p_new_msg;return 0;
}
Design Patterns 70
כפי שנית� לראות, מבנה מחלקת ה� dialog הוא כעת:
O(lg הינו קצר, ויצירת עצ� ההודעה החדש מבוצעת בסיבוכיות run() יעיל � קוד הפונקציה •
.map סיבוכיות חיפוש בטבלה � n)
• מודולרי � לא קיימת תלות בי� פונקצית ההצגה של מחלקת ה� dialog לבי� טיפוסי ההודעה.
למשל, אי� צור� לעדכ� פונקציה זו בהוספת סוג הודעה חדש.
• דינמי � נית� לקבוע בזמ� ריצה אילו טיפוסי הודעה זמיני� ע"י ה� Factory, כלומר, אפשר
להוסי� ל� Factory עצמי הודעה מטיפוסי� חדשי� או להסיר עצמי� קיימי�.
כמו כ�, מכיוו� שהכפתורי� מאותחלי� עפ"י וקטור ה� prototypes, אי� חשש לבילבול בי�
שמות טיפוסי ההודעה לבי� עצמי ההודעה עצמ�.
הכללה
Prototype-based Factory הוא מנגנו� לייצור עצמי� פולימורפי, תו� הפרדה בי� קוד הלקוח לקוד העצ� המיוצר: הוא מאפשר לקוד לקוח לייצר עצמי� מבלי לציי� את הטיפוס המדוייק של
העצ�, אלא רק מפתח שלו.
+clone()
«Prototype»AbstractProduct
Client
+create_object()+add_prototype()
Factory
m_prototypes
1*
+clone()
ProductA
+clone()
ProductB
• יתרונות:
− יעילות: ה� Factory מייצר עצ� עפ"י מפתח בסיבוכיות של O(lg n), כאשר n הוא
מספר ה� prototypes האפשרי.
− מודולריות: ה� קוד הלקוח וה� ה� Factory עצמו מבודדי� מהטיפוסי� המדוייקי�
של העצמי� ואינ� תלויי� בה�.
− דינמיות : נית� לקבוע בזמ� ריצה אילו טיפוסי� יהיו ניתני� לייצור בהקשר הנתו�.
פרק patterns : 3 בייצור עצמי� 71
יש לשי� לב לכ� שתוספת טיפוס Prototype אינה מצריכה א� לא הידור מחודש של
ה� Factory. המחלקה המשתמשת (בדוגמא הנ"ל, ה� dialog) אינה תלויה א� היא
.Prototypes בטיפוסי ה�
• וריאציות:
− מפתח ה� prototypes יכול להיות מחרוזת, מזהה מספרי או עצ� RTTI של מחלקת
.Prototype ה�
− ב� ++C נית� להגדיר template של Prototype-based Factory לשימוש כללי.
דוגמא: Prototype-based Factory כ� template ב� ++C לטיפוסי מפתח ו�
prototype כלליי�:
#ifndef FACTORY_H#define FACTORY_H#include <map>using namespace std;
// Factory class - Creates Objects by a key// - Singletontemplate <class Key, class Object>class Factory {public:
void add_proto(const Key key, const Object *proto) {m_prototypes[key] = proto;
}Object* create_object(const Key &key) {
return m_prototypes[key]->clone();}static Factory& instance() { // singleton access method
static Factory factory;return factory;
}~Factory() {
for(map<const Key, const Object *>::iterator i= m_prototypes.begin(); i != m_prototypes.end(); i++)
delete i->second;}
private:map<const Key, const Object *> m_prototypes;Factory() {}Factory(const Factory &);Factory& operator=(const Factory&);
};#endif