Design Patterns אליהו חלסצ'י תכנות מתקדם 89-210 תרגול מספר 10 תשע"א 2010-2011 Design Patterns אליהו חלסצ'י
הקדמה היום נלמד על Structural Patterns. SP עוסקים בשאלה כיצד מחלקות ואובייקטים מורכבים יחדיו כדי ליצור מבנים גדולים יותר. נעבור על: Adapter Bridge Composite Decorator Façade Flyweight Proxy
Adapter בעיה: כיצד נפתור את הבעיה? ה client יודע לעבוד דרך ממשק אחד, ואילו לנו יש מחלקה עם ממשק אחר או מתודות אחרות. להתחיל לשנות את הממשק של ה client כך שיתאים למחלקה שלנו יגרור המון שינויים בשאר המחלקות המממשות ממשק זה. אותו הדבר יקרה אם אשנה את הממשק של המחלקה שלנו... כיצד נפתור את הבעיה?
Adapter פיתרון: Object Adapter Class Adapter דרך אחת תהיה לממש את שני הממשקים ואז במתודה run נפעיל את play. אבל הדבר מפריע למחלקה MP3Player להיות מחלקה שמתעסקת רק עם mp3 מה יקרה אם יהיו עוד ממשקים לממש?? המצב רק יחמיר. פיתרון טוב יותר יהיה להשתמש ב Adapter יש שני סוגים: Object Adapter Class Adapter
Adapter פיתרון: Object Adapter ניצור מחלקה של Runnable שמחזיקה Reference לאובייקט ה MP3Player ובמתודה run נפעיל את ה play שלו. Object Adapter Class Adapter ניצור מחלקה של Runnable שיורשת גם מ MP3Player, ובמתודה run נפעיל את super.play() Class Adapter
Adapter המבנה הכללי: Class Adapter אפשרי למימוש ב ++C. אפשרי ב Java רק אם ה Target הוא ממשק. Object Adapter דרך יותר מקובלת ב Java. שאלות: 1. איזה adapter מסוגל לעבוד לבדו עם כל סוג של adaptee? 2. באיזה adapter יותר קל לשנות את התנהגות ה adaptee ?
Bridge מטרה: יצירת הפרדה בין האבסטרקציה לבין האימפלמנטציה כך ששתיהן תוכלנה להשתנות ללא תלות אחת בשנייה. דוגמא לבעיה: נתונה המחלקה האבסטרקטית house המייצגת בית, יש שלוש דרכים לבנות בית: קש, עץ ולבנים. אז הגיוני שנממש אותה בשלושת הדרכים: אבל מה יקרה אם נרצה בית נוסף? House StrawHouse WoodHouse BrickHouse BigHouse StrawBigHouse WoodBigHouse BrickBigHouse
Bridge פיתרון: נפתור את זה ע"י הפרדה בין האבסטרקציה House לאימפלמנטציה HouseImp מחלקות שמגדירות צורות שונות של בית יורשות מ House את המתודות שממומשות ע"י מופע של HouseImp.
Bridge מבנה: נשתמש ב Bridge כאשר: נרצה לנתק את הקשר בין המימוש לאבסטרקציה לדוג' כשנרצה להחליף מימוש בזמן ריצה. גם האבסטרקציה וגם המימוש אמורים להיות מורחבים ע"י ירושה. לא נרצה ששינוי במימוש האבסטרקציה ישפיע על ה client, כלומר לא נדרש לקמפל מחדש את הקוד. יש צורך לשתף מימוש בין מס' אובייקטים, ולהסתיר זאת מה client. השלכות: הפרדה זו מאפשרת להחליף אבסטרקציה בזמן ריצה ואף להחליף את האימפלמנטציה בזמן ריצה. הפרדה זו מעודדת "שכבות" וכך מובילה ל design יותר טוב של המערכת – השכבה הגבוהה יותר מכירה רק את abstraction ואת implementor. יכולת הרחבה משופרת – ניתן להרחיב (ירושה) גם את האימפלמנטציה וגם את האבסטרקציה בנפרד. הסתרת המימוש מה client.
Composite מטרה: הרכבת אובייקטים למבנה של עץ המייצג היררכיה של חלק-שלם, Composite מאפשר ל client להתייחס באותה הצורה לאובייקט אינדיבידואלי ולהרכבה של אובייקטים. השלכות: ה DP מגדיר היררכית מחלקות שמכילות primitive objects (עלים) וגם composite objects (קודקוד עם ילדים) בצורה בה רקורסיבית ניתן להרחיב אותם לעץ מורכב, בפשטות. ה client לא יודע (וגם לא צריך להיות איכפת לו) אם יש לו leaf או composite, ולכן זה הופך את הקוד שלו להרבה יותר פשוט. קל מאד להוסיף סוגים חדשים של leaf או composite כי זה לא משנה ל client. חיסרון: בגלל שקל מאד להוסיף סוגים חדשים, קשה לאכוף הגבלות – כשלדוגמא רוצים רק רכיבים מסוימים, נצטרך לבצע בדיקות בזמן ריצה.
Decorator מטרה: הוספה דינאמית של אחריות לאובייקט. מתי נשתמש: כשרוצים להוסיף אחריות באופן דינאמי, ואף שקוף לאובייקטים אחרים. עבור אחריות שיש להסיר כאשר הרחבה ע"י ירושה אינה מעשית בגלל מספר רב של אופציות, הגדרות מחלקה חבויות, או חוסר אפשרות לרשת ממנה. N תוספות בת"ל יוצרות 2N אפשרויות שהיינו צריכים לממש ע"י ירושה, או רק N מחלקות של Decorators.
Decorator השלכות: ה Decorator מאפשר הרבה יותר גמישות מאשר ירושה, ניתן להוסיף ולהסיר אחריות בזמן ריצה פשוט ע"י הצמדה או הפרדה שלו, בעוד שבירושה הינו צריכים ליצור מחלקה עבור כל קומבינציה של תוספת אחריות. ניתן לערבב תוספות של אחריות כרצוננו, ואף להוסיף אחריות כלשהי כמה פעמים. (לדוג' גבול כפול לשדה הטקסט) בהרחבה ע"י ירושה נצטרך לשלם מראש עבור כל האופציות גם אם לא נשתמש בכולן בעוד שב Decorator נוסיף אחריות תוך כדי ריצה ונשלם רק על מה שצריך. ה Decorator לא חייב להיות זהה לרכיב שהוא מחזיק ולכן אין לסמוך על כך כשמקבלים Decorator... ניתן להגיע למצב בו יש לנו המון מחלקות קטנטנות בעוד שקל לכייל ולדבג אותן למי שמבין אותן, לאחרים יהיה קשה.
Façade מטרה: מטרתו להוות ממשק יחיד לכמה ממשקים שונים בתת-מערכת מורכבת כלשהי. ה façade מגדיר ממשק high-level ובכך מקל על העבודה עם תת-המערכת. לתכנן מערכת מורכבת כאוסף של תת-מערכות זה design נכון, ומטרה נפוצה היא לצמצם ככל הניתן את התקשורת והתלות בין תת-המערכות הללו, וניתן להשיג זאת באמצעות ה façade. לדוגמא: יש לנו תת-מערכת לקימפול קוד היא בנויה ממחלקות כגון Parser, Laxer, SymbolTable , ByteCode ועוד. אולי יש clients שצריכים גישה ישירה למחלקות אלו, אך רוב ה clients רוצים פשוט לקמפל. המחלקה Compiler מהווה façade. ממשק
Flyweight בעיה: כיצד נפתור? אנו רוצים לממש משחק סודוקו, והגיוני שנצטרך אובייקט לייצוג ספרה בלוח. נשתמש בממשק Digit: כמה מופעים של Digit נצטרך? 1 5 3 7 2 8 4 6 9 נצטרך 81 מופעים של digit, ואף כפול אם נרצה לזכור גם את הפיתרון... המצב אף יחמיר בלוח סודוקו גדול יותר. כיצד נפתור?
Flyweight פיתרון: נפריד ב Digit בין state פנימי – intrinsic, שיכול להיות משותף, לבין sate חיצוני – extrinsic, שאינו בר שיתוף. הספרות 9..1 יכולות להיות משותפות אך מיקום הספרה לא. נסתפק ב 9 מופעים של Digit בלבד, כאשר כל תא בלוח הסודוקו יצביע אל המופע המשותף המתאים. כדי להדפיס ספרה במקום הנכון נעביר את השורה והעמודה כפרמטרים – שכן הם חיצוניים ואינם ניתנים לשיתוף. דוגמאות לסביבות נוספות: מעבד תמלילים. כריית טקסט. סיווג טקסטים. 1 5 3 7 2 8 4 6 9 1 2 3 4 5 6 7 8 9 Flyweight pool
Flyweight מטרה: להשתמש בשיתוף כדי לתמוך במספר גדול של אובייקטים קטנים בצורה יעילה. השלכות: אולי מבזבזים זמן על חישוב extrinsic states אך המון מקום נחסך ע"י שיתוף. רמת החיסכון מושפעת מהגורמים הבאים: ההפרש בין מספר האובייקטים שהיו נוצרים ללא ה DP הזה. כמות ה intrinsic states בכל אובייקט. האם ה extrinsic states מחושבים או שמורים.
Proxy מטרה: מתן ממלא מקום עבור אובייקט כלשהו, באמצעות ממלא המקום נשלטת הגישה לאובייקט. מדוע צריך ממלא מקום זה? לעיתים יש צורך במצביע מתוחכם או רב תכליתי לאובייקט: Remote Proxy מתן ייצוג מקומי עבור אובייקט במרחב כתובות אחר. Virtual Proxy יוצר אובייקטים "יקרים" ע"פ דרישה. Protection Proxy בודק הרשאות גישה לאובייקט. Smart reference מבצע פעולות נוספות כשיש בקשת גישה לאובייקט, כגון מונה מספר גישות, טעינתו לזיכרון בעת הגישה הראשונה או מניעה מאובייקטים אחרים לגשת אליו.
הטמעה מדוע נרצה להשתמש ב facade ? האם ניתן להפריד בין האבסטרקציה לאימפלמנטציה? מתי נעדיף להרחיב מחלקה ע"י ירושה, ומתי נעדיף וכיצד נרחיב אותה דינאמית? מנה יתרונות וחסרונות ל Decorator Pattern. האם ה Composite מתאים גם לייצוג של גרף? חישבו על DP שיתאים לניהול Cache.