design patterns - Decorator

bizon10

New member
design patterns - Decorator

הי,

כמה שאלות של מתחיל לגבי ה - pattern המעניין הנ"ל:

1. הבנתי שמדובר בעצם ב - wrapper, שמשקף החוצה את הממשק של האובייקט שהוא "מקשט", ומוסיף על זה עוד פונקציונליות. אבל אם עכשיו נוסף feature חדש ל - class שממנו נוצר האובייקט המקושט, הרי שה - wrapper לא ייצא את ה - feature החדש ונצטרך לשנות גם אותו. אני מבין נכון?

2. באותו נושא: ראיתי שבמקומות מסוימים מסבירים את ה - pattern הזה כך שמחלקת ה - decorator יורשת ממחלקת אב משותפת לה ולאובייקט המקושט. להבנתי, זו הדרך לאפשר ל - decorator לקשט כל מחלקת שנורשת ממחלקת האב הזו. אבל, מצד שני, אם מחלקת האובייקט המקושט מוסיפה פונקציונליות שלא קיימת במחלקת האב הזו (נניח פונקציה חדשה שלא קיימת כוירטואלית בממשק מחלקת האב), אז שוב, מחלקת ה - decorator לא תייצא את הפונקציונליות הזו...נראה קצת מגביל ויוצר צימוד בין המחלקות... יש פתרון לנושא? (אם בכלל הבנתי את זה נכון)

3. הבנתי איך אני "מלביש" decorator על אובייקט. איך מסירים decorator? קוראים ל - DTOR שלו?

תודה וחג שמח.
 

Nuke1985

Active member
תשובות

1) תלויי איך בדיוק נוסף הפיצר, אם הוספת מתודה חדשה אז כן, תצטרך לשנות את הממשק שממש האובייקט המקושט והדקוריטור ותצטרך לשנות את הדקורייטור שיתמוך המתודה החדשה (כלומר שיקרא למתודה של האובייקט המקושט, ויבצע את הפעולה של "קישוט" עליו אם היא רלוונטית), אבל אם כאשר אתה מוסיף את הפיצר אתה לא משנה את הממשק של האובייקט המקושט אז זה יהיה שקוף מבחינתו.

2) אתה צודק, זה באמת דורש עבודה ובאמת בספר מדובר על איך בsmalltalk יש פיצרים שמקלים על עבודת הפיתוח , בST (וכנראה שגם בשפות דינמיות אחרות, רוב הסיכויים שזה קיים ברובי ששואבת השראה מST) אתה לא צריך להצהיר על ממשק, יש לך רפרנס ואתה קורא לו עם שם של מתודה עם פרמטרים מסויימים (כלומר "שולח לו הודעה") והוא מפעיל את המתודה המתאימה. בנוסף לכך בST אפשר להגדיר מה אובייקט יעשה כאשר מופעלת עליו מתודה שהוא לא מכיר ואז אתה ייכול להגדיר לו פשוט להעביר אותה לאובייקט המקושט (אם אני זוכר, זאת מתודה שיש בכל אובייקט ואתה ייכול לעקוף אותה).

2) הדרך ההכי ישירה היא להחליף את כל הפוינטרים/רפרנסים של הדקורייטור באובייקט המקושט, אני מניח שאתה גם ייכול לממש בתוך הדקורייטור דגל בולייני "הפסק לקשט" כך שאם תהיה קריאה למתודה היא פשוט תועבר ישירות לאובייקט המקושט בלי הפעולה של "קישוט".
 
תוספות

חשוב להבין ש decorator שומר על ה interface (על ה"חוזה").
אם היה מרחיב אותו, הרי שהפסיק להיות decorator והפך להיות adapter (לפחות).
 


לפי מה שאני קראתי בספר "head first design patterns" הdecorator לא מוסיף שום פונקציונליות.
להפך.
אסור לו להוסיף פונקציונליות.

אגב, לעניות דעתי, זה קקה של design pattern וגם בספר די הסתייגו ממנו.
 
הכוונה היא *כן* להוסיף פונקציונליות

אבל לשמור על החוזה.
אם לא היינו מוסיפים פונקציונליות, אז היה לנו סתם מעטפת מנוונת (ומיותרת).
דוגמה מהנסיון שלי: כתבנו איזה reader שמחזיר byte array. "קישטנו" אותו ביכולת לעשות decompress. באותו אופן כתבנו writer שכותב byte array וגם אותו עטפנו ביכולת של compress.
החוזה נשאר כשהיה. התוכן השתנה.
אם נמשיך את הדוגמה הזו, אפשר גם להוסיף הצפנה ופענוח.

לעניות דעתי ולאור נסיוני, זו אחת מהתבניות השכיחות אצלי, לצד adapter. כך שמבחינתי היא רחוקה מלהיות קקה. ובכלל, מה זה המילים האלה?! פויה!
 

zaske

New member
תשובה מצויינת

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

bizon10

New member
תודה רבה!

כרגיל, יש את מי לשאול בפורום הזה:)

חג שמח.
 

zaske

New member
יש פתרון אלגנטי יותר ב Java

לסוגיית ה decorator . שימוש ב dynamic proxy + reflection או ב EJB3 interceptor (תלוי בצורך).
כמו כן , אפשר להשתמש גם ב AOP ולתת ביטוי שלכל מתודות של מחלקה מסויימת תוסיף לוגיקה מסויימת.

ועכשיו לשאלתך המקורית -

א. סביר להניח שכל מתודה ב wrapper תבצע פונקציונליות + קריאה למתודה באובייקט הנעטף, המעטפת והנעטף יממשו את אותו interface (או ירשו מאותו class ). חלק מהרעיון של decorator הוא שממבחינת שימושיות תוכל לכתוב משהו כמו

MyObj obj = new MyObjDecorator(new MyObj());

כלומר, הטיפוס הסטטי יהיה MyObj אבל הדינאמי יהיה ה wrapper . אם MyObj היא אכן מחלקה , והדקורייטור יורש ממנה , ואתה תוסיף לה פונקצנליות, בלי להוסיף ל decorator זה יתקמפל לך, אבל הפונקציונליות החדשה לא תהיה מקושטת, ולכן MyObj ו MyObjDecorator צריכים "אב קדמון" משותף לרשת/לממש אותו.

ב. על מנת להסיר decorator - אתה צריך לספק ב decorator מתודה שמקבלת את האובייקט הנעטף - זאת על מנת שיהיה לך רפרנס אליו אחרי ההריסה (זה לא חובה, אולי יש לך רפרנס אליו לפני שעטפת).
אתה צריך לדווא שיש דרך דרך לסמן ל dtor של ה decorator לא להרוס את האובייקט הנעטף כשהוא נהרס (אפשר אולי להגדיר משהו כמו ShallowDecorator ו DeepDeocrator שאחד מהם יהרוס את האובייקט הנעטף והשני לא).
 

zaske

New member
שכחתי גם להזכי את cglib ב java

בעזרת dynamic proxy
ejb3 interceptor
ו cglib
אפשר להוסיף קוד מקשט.
 

bizon10

New member
תודה zaske

אחרי עוד קצת חפירה נתקלתי באמת בדרך שציינת להסרת decorator...
זו פעם ראשונה שאני ממש לומד design patterns, אבל כן, אני לומד אותן בצורה מסויגת ולא מקבל אותן כדבר מוחלט (בין היתר בזכות הודעות שלך בנושא בעבר).
 

Nuke1985

Active member
הערות

הרושם שלי הוא שאנשים חושבים שדיזיין פטרן הם משהו שאתה צריך ליישם בהזדמנות הראשונה שיש לך ובכל פעם שזה אפשרי, זה ממש לא נכון והgof הסבירו שיש להם חסרונות ולא צריך להשתמש בהם אם אתה לא צריך את הגמישות שהם נותנים (הם אמרו את זה גם על דיזיין פטרנס בכללי וגם נתנו חסרונות ספציפיים של פטרנים ספציפיים).

בכל מקרה לדעתי אחד הכללים ההכי חשובים (אם לא החשוב ביותר) כאשר לומדים פטרנים הוא "אם אתה לא יודע מתי אפשר אבל לא כדאי להשתמש בפטרן, אתה לא באמת מבין אותו".

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

zaske

New member
גם במערכות מורכבות/אפילקציות מורכבות

כדאי לשמור על דיזיין טוב וקל להרחבה, על מנת שהקוד שלך לא יהפוך לספטגי (כשר לפסח כמובן)
 

Nuke1985

Active member
אתה צודק אבל

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

בנוסף לכך מה "קל להרחבה" תלויי במה אתה מנסה לעשות ומה השיקולים שלך. לדוגמה כל שרשרת של if else ניתן להחליף בשימוש בפולימורפיזם (ככה שניתן לקנפג את מה שיבוצע בזמן ריצה ולהרחיב את זה בלי לשנות את הקוד שמריץ את האובייקטים הפולימורפיים), אז מה יותר קל? להוסיף עוד else if? או ליצור מחלקה שממשת ממשק עם לפחות שתי מתודות וירטואליות (משהו כמו checkShouldRun ו execute) ולדאוג למעבר של המידע הרלוונטי מהמחלקה הראשית למחלקה הזאת? זה תלויי מה השיקולים שלך, אם אתה רוצה שהבן אדם שרוצה להרחיב את הדיזיין שלך לא יצטרך לגעת בקוד של המחלקה הראשית (שיקול מאוד נפוץ כאשר מתכננים סיפריה) אז זה כנראה שיותר הגיוני להשתמש בפולימורפיזם, אבל במקרה הממוצע הקוד שלך יהיה יותר קל להרחבה אם הוא יהיה הכי פשוט שאפשר ובמקרה הזה עדיף פשוט להשתמש ב else if.
 

choo

Active member
המימוש יכול להיות שונה ומשונה, בשפות שונות

הנה תכונות של שפות שמאפשרות לעשות דברים מוזרים לאובייקטים (לאו דווקא בצורה שישימה לתבנית הזו):

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

ב. ב-objective C (לפחות ממה שהכרתי בשנות ה-90), יכולת לשנות את היררכיית המחלקות בצורה דינמית בזמן ריצה, כולל הוספה של super-class עבור מחלקה קיימת - בזמן ריצה. עקרונית, היית יכול להוסיף מחלקת אב לאובייקט, ופתאום כל ההתנהגות שלו מסתדרת אחרת. זה יכול לאפשר לך לבצע דקורציה לפעולות שקוראות למחלקת האב שלהן - לאו דווקא דקורציה באופן כללי ושקוף.

אחת הבעיות של הספר המקורי של design patterns היא - שהוא תלוי-שפה. יש תבניות שהצורך בהן נעלם כשעוברים לשפת תיכנות אחרת. האם הספר השפיע על מעצבי השפות הללו? ייתכן שכן, במקרים מסויימים. בטוח שלא במקרה ב (שהיה קיים הרבה לפני יציאתו לאור של הספר). במקרה א - ייתכן שכן.
 

zaske

New member
גם ב java

אפשר לעשות אינסטרומנטציה בזמן ריצה ולשנות ירושות.
היה לנו באג של Java EE - יש התנהגות של Java EE שאומרת שאם bean מממש interface אחד, הוא יהיה local בלי שהמפתח יהיה צריך לציין זאת מפורשות (כלומר, יוכל לשמש כ"ממשק" להפעלה של ה bean באותה אפליקציה על אותו מחשב).
היה איזה כלי אוטומטי שביצע אינסטרומנטציה (נראה לי לצרכי profiling ) והוסיף interface למחלקות, כתוצאה מכך, bean שהיה לו ממש יחיד קיבל עוד ממשק והכלל נשבר - פתאום קיבלנו שגיאה שיש יותר מ interface אחד וצריך להגיד מפורשות מי ה Local.

אני כמעט בטוח לגבי הספר המקורי שהוא ניתן למימוש בכמעט כל שפת OO "מודרנית" (לא רוצה להתחייב כמובן). לגבי אם הצורך באמת קיים בכל שפה -זה משהו אחר.
 

nocgod

New member
גם לא לא חריף בתבניות עיצוב אבל

סתם שאלה, בגדול אם כל הרעיון של הdecorator הוא בגדול להרחיב פונקציונליות ולקשט, לצורך העניין ב C# אפשר להשתמש בextension methods כדי להשיג אותו פיתרון? או שאני רחוק מהמטרה?
 

zaske

New member
לא חריף בסי שארפ

אבל ממה שידוע לי על extension methods - יש מצב.
הרעיון בדוקרייטור הוא שהממשק המקורי לא נשבר.
כלומר, מישהו שהוא הצרכן של האובייקטים הוא אגנוסטי להאם האובייקט מקושט או לא.
כשאתה מפעיל מתודה של אובייקט מקושט, לרוב מה שיקרה זה הפעלת קוד קישוט + הפעלת הקוד המקורי שלה.
כמובן שהסדר לא מחייב - יכול להיות שיש "קישוט לפני", הפעלה מקורית, "קישוט אחרי" וכו'.
 
קצת דחוק

1. ב EM אתה יכול לממש רק שכבה עוטפת אחת, לכל היותר.
2. ההרחבה ב EM היא סטטית, והיא נקבעת בזמן ה build. הרעיון ב decorator הוא ליצור גמישות בזמן הריצה, כלומר לבחור בזמן הריצה עם איזה decorator עובדים (אחד או יותר).
 

zaske

New member
אתה צודק לגמרי

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

nirrub

New member
AOP זה קצת רעה חולה לטעמי

יש לך קוד שמוזרק לאובייקט שלך ממקור חיצוני שמאוד קשה לראות אותו כאשר אתה מסתכל ב source code.
ב decorator רגיל, בד"כ הקוד מול העיניים כי פשוט מדברים על מחלקה אחרת שמממשת את אותו ממשק.

לגבי ה AOP שאני מכיר, ההחלטה על איזה אובייקט מבצעים Weaving נעשית ע"י הוספת Attribute כלשהו על האובייקט או חלק מהמתודות שלו - כך יש לך explicit weaving
 
למעלה