The Factory Design Pattern

שלום לכולם, היום אנחנו ממשיכים במסע שלנו ברחבי ה-design pattern.
והפעם אנחנו נדבר על אחד שיוצא לי להשתמש בו די הרבה: The Factory Design Pattern.

יש הרבה דרכים ליצור אובייקטים מעבר לאופרטור new.
בפוסט הזה אנחנו נלמד שיצירה של אובייקטים היא פעילות שלא תמיד כדאי לעשות אותה ציבורית (public)
כיוון שלפעמים היא יכולה לגרום לבעיות coupling (מוזמנים להסתכל על הפוסט של observer להסבר)
ואנחנו לא רוצים בעיות coupling נכון?
אז בפוסט הזה נלמד ביחד איך Factory Design Pattern יכול להציל אותנו מתלויות מביכות

כשאתם רואים new תחשבו concrete

כאשר אנחנו משתמשים באופרטור new אנחנו בהכרח מאתחלים concrete class
כלומר אנחנו כבר עוברים על העקרון של open close
ובנוסף, זה יוצר תלות בין ה-class הנוכחית שאנחנו כותבים ל-class שאנחנו יוצרים
ומכאן שזה מקטין את הגמישות של ה-class שאנחנו כותבים

אם נסתכל על השורה הזאת

Duck duck = new MallardDuck();

אנחנו רוצים להשתמש באובייקטים אבסטרקטים כמו Duck אבל בפועל אנחנו יוצרים concrete class.
וכאשר יש לנו אוסף של concrete class יש לנו קוד שנראה ככה

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

אז מה הבעיה עם new

ובכן, טכנית אין בעיה עם new. זה חלק בסיסי בהרבה מאוד שפות מודרניות.
הבעיה היא עם הקבוע של כל קוד, השינוי
כאשר אנחנו עוקבים אחרי העקרון של code to interface
אנחנו מבודדים את הקוד שלנו מהרבה שינויים שיכולים להיות במימושים ספציפיים של מחלקות
אנחנו מחביאים את השינוי הזה באמצעות polymorphism
אבל כאשר יש לנו קוד אשר מאתחל הרבה concrete classes
אנחנו מזמינים לעצמו צרות, כי כאשר יגיע שינוי, אנחנו נצטרך להתאמץ כדי לטפל בו

מישהו אמר פיצה?

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

אנחנו מעבירים ל-orderPizza את ה-type שאנחנו רוצים ליצור.
לפי ה-type אנחנו מחליטים איזה concrete class ליצור.
כל פיצה צריכה לממש את ה-interface Pizza
אחרי שאנחנו מסיימים ליצור את הפיצה, אנחנו מבצעים סדר פעולות קבוע.

רק ארבע פיצות?

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

נשים לב שהחלק בקוד שהשתנה זה הרצף של ה-if-else
ומה שנשאר קבוע זה הפעולות שאנחנו עושים על הפיצה
זה צועק לשמיים שאנחנו צריכים לבצע אנקפסולציה על הקוד שמשתנה

כאן נכנס לתמונה ה Factory.
אנחנו ניצור מחלקה חדשה, שאחראית ליצור את הפיצה שלנו לפי ה-type
זו כל האחריות שלה. היא תראה ככה

מה היתרונות של הגישה הזאת? כל מה שעשינו פה זה לדחוף את הבעיה למקום אחר
דבר שחשוב לזכור פה הוא של-SimplePizzaFactory יכול להיות הרבה לקוחות
הרבה מקומות בהם קוראים ל-createPizza
למשל יכולה להיות לנו מחלקה שמייצגת את התפריט של הפיצריה – PizzaShopMenue – והיא קוראת ל-Factory שלנו על מנת לקבל את הפרטים של הפיצה
או שיש לנו בכלל שירות משלוחים, שמטפל בפיצות בצורה אחרת
ולכן בכך שריכזנו את היצירה של הפיצות במקום אחד, אנחנו מרכזית את כל השינויים למקום אחד
וחוסכים שינויים במקומות אחרים, אלמלא נקטנו בגישה הזאת

אז עכשיו ה-PizzaStore שלנו תראה ככה

הגדרה של Factory Design Pattern

מה שהראיתי עד עכשיו נקרא Simple Factory
הוא לא באמת Design Pattern.
אבל הוא אבן הבסיס של החלקים הבאים בהמשך.
הנה ההגדרה שלו

תרשים UML של Simple Factory

ה-PizzaStore הוא ה-client של ה-factory.
הוא עובר דרך ה-Simple Factory כדי לקבל instance של פיצה
ה-SimplePizzaFactory, הוא בעצם ה-factory שלנו. שם נוצרים ה-instance של הפיצות
יש לנו את Pizza, שזה בעצם ה-product של ה-factory.
במקרה הזה יש לנו מחלקה אבסטרקטית, עם הרבה מחלקות קטנות שיורשות אותה

הרשת שלנו מתרחבת

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

מימוש של כמה PizzaStore עם Simple Factory
מימוש של כמה PizzaStore עם Simple Factory

איך זה יראה כקוד?


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

מה שאנחנו הולכים לעשות, זה שאנחנו הולכים להחזיר חזרה את createPizza חזרה לתוך PizzaStore.
כן מה ששמעתם, עבדנו קשה להוציא ועכשיו מחזירים, תכף אסביר
הקוד של PizzaStore יראה ככה:


ההבדל עכשיו הוא שאנחנו ניתן ל-subclass להחליט איך הפיצה נוצרת
למשל אם נסתכל על NYStylePizzaStore זה יראה ככה


אוקי, בו הו, פיצלנו דברים ל-subclass?
איפה באמת ה-subclass האלו מחליטים משהו? מעבר ל-type?

העניין הוא כשאר אנחנו מסתכלים מהכיוון של ה-PizzaStore
נזכור שבגרסה הזאת, PizzaStore היא אבסטרקטית.
וכאשר אנחנו מגיעים לבצע את orderPizza, הקוד כתוב ב-Pizza אבל מי שבפועל מריץ אותו זה אחת ה-subclasses של פיצה
כאשר orderPizza קוראת ל-createPizza כדי לקבל אובייקט, היא לא יודעת איך הוא נוצר. היא לא יודעת איזו pizza היא מכינה
מי כן יודעת? ה-subclass כמובן

Factory Method

מה שבעצם ראינו פה נקרא Factory Method
אנחנו בעצם משתמשים ב-method על מנת ליצור factory אשר ייצר לנו את האובייקטים שאנחנו רוצים.
הקוד המלא של PizzaStore נראה ככה:


אנחנו מגדירים את createPizza כabstract, על מנת להכריח כל subclass של PizzaStore
התבנית של Factory method היא כזאת

abstract Product factoryMethod(String type)

ה-factoryMethod מחזירה את ה-Product שהיא מייצרת.
בעצם המתודה הזאת מבודדת את ה-client של ה-factory מהידיעה איזה אובייקט מייצרים
שימו לב שהפרמטר לא חייב להיות String, יכול להיות כל דבר אשר לפיו מחליטים איזה product לייצר

אוקי בואו נסתכל עכשיו על דוגמה מלאה של factory method

והפלט נראה ככה

--- Making a NY Style Sauce and Cheese Pizza ---
Prepare NY Style Sauce and Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Grated Reggiano Cheese
Bake for 25 minutes at 350
Cut the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Sauce and Cheese Pizza

--- Making a Chicago Style Deep Dish Cheese Pizza ---
Prepare Chicago Style Deep Dish Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Shredded Mozzarella Cheese
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Deep Dish Cheese Pizza

--- Making a NY Style Clam Pizza ---
Prepare NY Style Clam Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Grated Reggiano Cheese
   Fresh Clams from Long Island Sound
Bake for 25 minutes at 350
Cut the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Clam Pizza

--- Making a Chicago Style Clam Pizza ---
Prepare Chicago Style Clam Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Shredded Mozzarella Cheese
   Frozen Clams from Chesapeake Bay
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Clam Pizza

--- Making a NY Style Pepperoni Pizza ---
Prepare NY Style Pepperoni Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Grated Reggiano Cheese
   Sliced Pepperoni
   Garlic
   Onion
   Mushrooms
   Red Pepper
Bake for 25 minutes at 350
Cut the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Pepperoni Pizza

--- Making a Chicago Style Pepperoni Pizza ---
Prepare Chicago Style Pepperoni Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Shredded Mozzarella Cheese
   Black Olives
   Spinach
   Eggplant
   Sliced Pepperoni
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Pepperoni Pizza

--- Making a NY Style Veggie Pizza ---
Prepare NY Style Veggie Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Grated Reggiano Cheese
   Garlic
   Onion
   Mushrooms
   Red Pepper
Bake for 25 minutes at 350
Cut the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Veggie Pizza

--- Making a Chicago Deep Dish Veggie Pizza ---
Prepare Chicago Deep Dish Veggie Pizza
Tossing dough...
Adding sauce...
Adding toppings: 
   Shredded Mozzarella Cheese
   Black Olives
   Spinach
   Eggplant
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Deep Dish Veggie Pizza


את הקוד המלא אתם יכולים למצוא כאן

הכרות רשמית עם Factory Method Design Pattern


אז אחרי שראינו דוגמה בואו נסתכל על ההגדרה הרשמית.
יש ל-factory method כמה חלקים עיקריים

  • ה-creator: במקרה שלנו זה ה-PizzaStore. לרוב יש בו קוד אשר מבוסס על ה-product האבסטרקטי שאותו הוא יוצר.
    ה-product נוצר על ידי אחת מה-subclasses של ה-creator.
  • ה-product: במקרה שלנו זה כמובן ה-Pizza. גם לו יש subclasses כדי לייצג את הסוגים השונים


ה-factory method מגדיר interface ליצירת אובייקטים
אבל נותן ל-subclasses שלו להחליט איזה class נוצר בדיוק.

UML של factory method
UML של factory method

סיכום

כמו תמיד, אשמח לקבל כל הערה, הארה או שאלה שיש לכם
אשמח אם תפיצו את הפוסט

זהו להפעם. למדנו הרבה היום
קודם כל מה הבעיה עם new, ולמה לפעמים אנחנו רוצים להחביא את יצירת האובייקטים שלנו
דיברנו גם על Simple Factory וגם על Factory Method.
ובסוף ראינו איך הם עוזרים לנו ליצור קוד גמיש יותר וקל להרחבה

השאר תגובה

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

Scroll to Top