Builder Pattern – חלק א

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

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

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

בוב הבילדר =]

הגדרת התבנית

Builder Pattern הינה תבנית עיצוב המאפשרת לנו לייצר אובייקט מורכב בצורה הדרגתית ולהגדיר כל פרמטר באובייקט בצורה נפרדת. בעצם מדובר על תבנית שמאפשרת לנו לבנות אובייקט צעד אחרי צעד.

למה אני צריך את זה?!

נניח שאנחנו רוצים לייצר אובייקט מסוג מכונית, כל שעלינו לעשות הוא לקחת שילדה, להוסיף לה 4 גלגלים, 5 כסאות, לצבוע אותה באדום, להוסיף לה גיר אוטומטי ולשים לה סמל של הונדה מקדימה. אבל מה יקרה אם נרצה שהמכונית שאנחנו מיייצרים תהיה בכלל מכונית מרוץ עם שילדה מיוחדת, 2 כסאות, גיר ידני, בצבע שחור ועליה סמל של פרארי? או אם נרצה בכלל מכונית למשפחה גדולה עם 7 כסאות, בצבע כחול, עם תא מטען גדול וסמל של מאזדה מקדימה?

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

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

אז מה אתה מציע לי בעצם?

Builder Pattern מציעה לנו לחשוב בצורה אחרת על כל הבעיה. במקום שאנחנו נארגן למשתמש את האובייקט ונכתוב עבורו בנאי או כל מיני קומבינציות, ניתן למשתמש לייצר את האובייקט שהוא רוצה צעד אחרי צעד, אנחנו רק ניתן לו את האפשרות להגדיר בכל צעד עוד חלק באובייקט עד שהוא יסיים להגדיר את כל הפרמטרים הרלוונטיים לאובייקט שלו.

התבנית מציעה לנו לסדר את יצירת האוביייקט כסט של פעולות הנתונות לבחירת המשתמש. לדוגמה: כדי להגדיר מספר דלתות נקרא לפונקציה WithNumberOfDoors, כדי להגדיר צבע נקרא לפונקציה WithColor וכדו’. אחד היתרונות המשמעותיים פה זה שלמשתמש יש את האפשרות להחליט לאיזה פונקציות הוא רוצה לקרוא ולאיזה לא.

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

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

Builder Pattern – תיאור מחלקות האובייקטים

הגדרנו מחלקה כללית – Car.
למחלקה זו הגדרנו שני “בנים” אחד מייצג מכונית מרוץ והשני מכונית פרטית.
פשוט מאוד.

כעת בואו נראה כיצד יראו המחלקות האחראיות על בניית המכוניות:

Builder Pattern – תיאור מחלקות הבנאיים

אולי זה נראה קצת מסובך יותר, אבל זה ממש לא ככה.

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

בנוסף, לכל בנאי יש שתי פונקציות שחשוב לשים אליהם לב:
()Reset – פונקציה זו מאפסת לנו את הבנאי ומחזירה אותו למצב ההתחלתי, כך נוכל להשתמש באותו בנאי ליצירה של אובייקטים שונים.
()Build – לפונקציה זו נקרא בסוף התהליך של בניית האובייקט, היא תסגור לנו את האובייקט שבנינו ותחזיר לנו את האובייקט הרצוי.

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

מקרים כללים בהם נרצה להתשמש בתבנית זו

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


זה מספיק להפעם.
פעם הבאה נמשיך לחלק המעשי, נראה את הקוד שמממש את התבנית הזו, ונבין עד הסוף את הדרך הנכונה לעבוד עם Builder Pattern.
תבואו, יהיה מעניין!

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *