מישהו פתר לכם כבר את הבעיה!
בפוסט הזה אנחנו נלמד למה (ואיך) אתם יכולים להשתמש בחוכמה של מפתחים אחרים על מנת לפתור בעיות עיצוב שיש לכם היום.
אנחנו נבין מה היתרונות בשימוש ב-design patterns, ונבין איך design pattern עובד.
הכל התחיל בברווז
אנחנו נתחיל את המסע שלנו עם design patterns בדוגמה.
נניח והתבקשם לפתח משחק של בריכת ברווזים.
המשחק שלנו יכול להראות מגוון עשיר של ברווזים ששוחים בבריכה ומגעגעים.
העיצוב הראשוני לפיתוח נראה ככה:
Duck היא מחלקה אבסטרקטית.
כל הברווזים שלנו מגעגעים ושוחים באותה צורה.
ולכן המימוש יהיה במחלקה Duck.
המתודה display היא אבסטרקטית, כיוון שכל ברווז נראה אחרת.
בעצם כל sub-class אחראי לממש את display.
יש לנו עוד הרבה ברווזים מערכת, אבל אני קצת עצלן לצייר את כולם 😉
הכל טוב ויפה במחשק שלנו, רק שמתישהו מבקשים מאיתנו להוסיף אופציה חדשה במשחק.
עכשיו רוצים שהברווזים שלנו גם יעופו.
ככה נראה המימוש עכשיו
נראה בסדר לא?
כל הברווזים שלנו יכולים עכשיו לעוף.
אז מסתבר שלא
הכירו את ברווז הגומי
מסתבר שיש לנו ברווז שלא אמור לעוף בכלל
אז מה בעצם קרה פה?
בזה שהוספנו את fly ל-Duck דחפנו את ההתנהגות לברווז הגומי.
ויהי באג.
אז מה הבעיה? נדרוס את המימוש של fly בברווז גומי ככה שהוא לא יעשה כלום
אבל מה יקרה אם נרצה להוסיף ברווז חדש, שהוא לא מגעגע ולא עף?
כמו למשל ברווז עץ?
גם שם נדרוס את המחלקות שלא רלוונטיות לברווז?
אני מקווה שאתם רואים שזה כבר נהיה מסובך, וככל שיעלו סוגי הברווזים הקוד שלנו יהיה פחות קריא
כי אפשר באמת לדעת מה כל ברווז עושה
אז אולי interface?
אז אם ירושה היא לא הפתרון שלנו, אז אולי נעבור לעבוד עם interfaces?
נגדיר interface עבור כל הברווזים שצריכים לגעגע, ועבור כל הברווזים שצריכים לעוף
וכל ברווז יממש איזה interface שהוא רוצה
אבל רגע. לא אמרנו שרוב הברווזים שלנו שוחים ומגעגעים באותה דרך?
בפתרון הזה אנחנו הולכים לשכפל קוד. והרבה.
כי כל ברווז עכשיו יצטרך לממש את quack (או לפחות כל אלו שמגעגעים, שזה רובם).
אז מה אנחנו צריכים לעשות?
לא כל הברווזים שלנו צריכים לגעגע או לעוף, אז ירושה לא עובדת
אבל אנחנו גם לא רוצים לשכפל את הקוד עבור כל מה שמשותף.
בנקודה הזאת אפשר להציג את ה design pattern שיפתור לנו את הבעיה.
אבל מה הכיף בזה. איך נבין ככה?
אנחנו נעשה את הדרך הארוכה ונבין ביחד למה הפתרון הסופי באמת עובד.
הקבוע היחידי בפיתוח תוכנה
מהו הדבר היחידי שקבוע בפיתוח תוכנה?
לא משנה מה אתם כותבים, באיזו שפה, ומה ה business logic שלכם?
ובכן זה שינוי.
לא משנה כמה טוב אנחנו נעצב את התוכנה שלנו, תמיד יהיה צורך בשינוי בעתיד.
מה יכול לגרום לשינוי בקוד שלנו?
- הלקוחות שלנו רוצים משהו חדש, או אחר ממה שקיים היום
- החברה מחליטה לעבור לframework אחר, או database אחר.
- הטכנוליגה מתקדמת ואנחנו מריכים להחליף פרוטוקולים
- אנחנו מחליטים לעצב מחדש, כי ההנחות הראשונות שלנו כבר לא נכונות.
להפריד בין מה שמשתנה למה שנשאר קבוע
אז איפה אנחנו מתחילים?
מעבר לבעיה שיש לנו עם fly ועם quack שאר המחלקה Duck עובדת בסדר גמור
אנחנו נשאיר אותה כרגע פחות או יותר אותו דבר
עכשיו אנחנו רוצים להפריד את החלק שמשתנה.
אנחנו ניצור שתי קבוצות של מחלקות חדשות, אחד עבור quack ואחת עבור fly
כל מחלקה יכולה לממש בצורה שונה את הפעולה.
למשל מחלקה אחת תממש את quacking, אחרת תממש את squeaking והאחרונה תממש את silent
תכנון התנהגויות של ברווז
אז איך אנחנו הולכים לעצב את אוסף המחלקות הללו?
אחנו רוצים שהן יהיו גמישות, ואנחנו רוצים שברווזים יוכלו להשתמש בהן.
בנוסף, אנחנו רוצים להיות מסוגלים לשנות התנהגות בצורה קלה.
אז זה אומר, שאנחנו רוצים לקבל את זה בבנאי, וגם להוסיף איזה שהוא setter.
ולכן זה מביא אותנו לדבר על code to interface.
הכוונה היא שאנחנו הולכים להשתמש כמה שיותר בintefaces בתור שדות, במקום במחלקות ספציפיות
אנחנו נשמש ב interface כדי לייצג קבוצה של התנהגויות, כלומר אחד לquack ואחד לfly
הנה למשל דוגמה של fly
אבל למה אנחנו צריכים בכלל inteface פה? ולא מחלקה אבסטרקטית?
העקרון של code to interface לא בהכרח אומר inteface
אלא הוא מתכוון לsuper type, שזה או מחלקה אבסטרקטית או interface.
איך נחליט?
הכלל אצבע שלי הוא שאם יש קוד משותף לכל הsub classes עדיף להשתמש במחלקה אבסטרקטית
אבל אם אין, אלא אנחנו רק רוצים להגדיר אילו מתודות חייבים להשתמש, ומה השדות המשותפים, אז אני ממליץ על interface
איך תראה Duck אחרי השינוי הזה?
חפרת מספיק, תראה קצת קוד
אתם צודקים בואו נסתכל על קוד
כמו שאתם יכולים לראות, המחלקה Duck מכילה שני שדות עבור ההתנהגות
וכאשר אנחנו רוצים לבצע את ההתנהגות, אנחנו קוראים לperform המתאים
שימו לב שבמימוש הזה, כל הברווזים שוחים
עכשיו נסתכל על הכל הFlyBehavior
כאן יש לנו את ה interface של FlyBehavior
הוא מגדיר רק פונקציה אחת, את fly
יש לנו שני מימושים שונים. אחד יכול לעוף, ואחד לא
יאללה אל הquack
גם כאן הקוד די ברור
כל התנהגות של quack מבוצעת באופן שונה
אז איך כל זה מתחבר לידי ברווז אחד?
כל ברווז בבנאי שלו יגדיר מה ההתנהגויות שהוא מבצע
כזה פשוט
הנה דוגמה של הכל מתחבר
והפלט שלנו הוא
Quack I'm flying
וככה מבלי ששמתם לב למדנו את ה design pattern הראשון שלנו, שהוא ה-strategy
שבהגדרה הרשמית שלו:
מגדיר משפחה של אלגוריתמים, ומבצע אנקפסולציה על כל אחד מהם, וככה נותן להם גמישות Strategy נותן לנו אופציה להחליף מימוש של אלגוריתמים בצורה נוחה ויעילה
לסיכום
בפוסט הזה הצגתי למה design patterns הם כל כך חשובים.
בעיקר בגלל שהם נותנים לנו פתרונות לבעיות שרובנו ניקתל בצורה כזאת או אחרת
וכבר הרבה אנשים חשבו על זה ומצאו פתרונות די טובים
הסברתי גם מה הבעיה לפעמים עם עקרונות פשוטים של OOP, ואיך אפשר לפתור אותם
וכבונוס למדנו ביחד Strategy design pattern
את כל הקוד אתם יכולים למצוא בגיטהאב הזה
וכמו תמיד, אשמח לקבל כל הערה, הארה או שאלה שיש לכם
פינגבאק: Observer Design Pattern - שורת קוד
זה נקרא strategy design pattern (אסטרטגיה ), ולא stradgey
תודה רבה על התיקון
תיקנתי במקור