648
0

מסבירים את תבנית העיצוב Factory Method

648
זמן קריאה: 4 דקות

בפוסט הזה נלמד את אחד מתבניות העיצוב הנפוצות ביותר בעולם תכנות מונחה העצמים OOP.

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

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

תוכן עניינים

אז מה זה Factory Method?

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

Factory pattern הינה תבנית עיצוב יצרנית (Creational) , 
במקום ליצור אובייקטים בצורה ישירה בקוד שלנו, נקרא לפונקציית “factory” שזהו התפקיד שלה.
כלומר, לא יופיע בקוד שלנו יצירת אובייקת חדש ע”י האופרטור new באופן ישיר, אלא על ידי קריאה למתודה כלשהי שהיא אחראית על הבניה של האובייקט הנדרש, ובסופו של דבר תחזיר לנו אובייקט מוכן לשימוש.

דוגמה

הדוגמא נמצאת גם בGitHub
ואם גם עכשיו עוד לא הבנתם הכל בסדר, אין כמו דוגמה שתעזור – 

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

כך נראה הקוד שלנו ללא תבנית העיצוב – (הכל קורה באותה מחלקה)

				
					Pasta OrderPasta(String type) 
{
    Pasta pasta;
    if (type.equals(“Bolognese”)) 
    {
        pasta = new Bolognese();
    } 
    else if (type.equals(“FettuccineAlfredo”) 
    {
        pasta = new FettuccineAlfredo();
    } 
    else if (type.equals(“SeafoodPasta”) 
    {
        pasta = new SeafoodPasta();
    } 
    else if (type.equals(“SpinachRavioli”) 
    {
        pasta = new SpinachRavioli();
    } 
    else if (type.equals(“veggie”) 
    {
        pasta = new VeggiePasta();
    }
    
pasta.prepare();
pizza.cook();
pizza.box();
return pasta;
				
			

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

כימוס (Encapsulating) תהליך יצירת האובייקטים –

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

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

				
					public abstract class Pasta
{
    private List<Ingredient> _ingredients = new List<Ingredient>();
    
    public Pasta()
    {
        CreateIngredients();
    }
    
    public abstract void CreateIngredients();
    
    public List<Ingredient> Ingredients => _ingredients;
}
				
			

והנה מגיע המפעל שלנו – זה שלוקח את האחריות “להגיש” / “ליצור” מחלקות על פי מידע מוקדם (סוג הפסטה במקרה הזה),
שימו לב שזהו התפקיד היחידי של המחלקה (במקום להשתמש בif הוספתי enum בשילוב עם switch case כדי ליצור קוד קריא ומסודר אך ממש לא חייבים.)

				
					public class Enums
{
    public enum PastaType
    {
        Bolognese = 0,
        FettuccineAlfredo = 1,
        SeafoodPasta = 2,
        SpinachRavioli = 3,
        VeggiePasta = 4,
    }
}
				
			
				
					public class SimplePastaFactory 
{
    public Pasta CreatePasta(String type) 
    {
        public static Pasta CreatePasta(Enums.PastaType pastaType)
        {
            switch (pastaType)
            {
                case Enums.PastaType.Bolognese:
                    return new Bolognese();

                case Enums.PastaType.FettuccineAlfredo:
                    return new FettuccineAlfredo();

                case Enums.PastaType.SeafoodPasta:
                    return new SeafoodPasta();

                case Enums.PastaType.SpinachRavioli:
                    return new SpinachRavioli();

                case Enums.PastaType.VeggiePasta:
                    return new VeggiePasta();

                default:
                    return null;
            }
        }
    }
}
				
			

 

וכעת אחרי שהעברנו את האחריות למפעל ניתן לראות את השינוי במחלקה הבאה – 

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

				
					public class PastaStore 
{
    SimplePastaFactory factory;
    
    public PastaStore(SimplePastaFactory factory) 
    {
        this.factory = factory;
    }
    
    public Pasta orderPasta(String type) 
    {
        Pasta pasta;
        pasta = factory.createPasta(type);
        pasta.prepare();
        pasta.cook();
        pasta.box();
        return pasta;
    }
    
    // other methods here
}
				
			

סיכום

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

להלן הגדרה פשוטה יותר שנכתבה על ידי Gang of Four (הרביעייה שיצרו את תבניות העיצוב): 
“הגדר ממשק ליצירת אובייקט, ותן למחלקה אחרת להחליט איזה מחלקה ליצור.”

אמיר שטיימן
WRITEN BY

אמיר שטיימן

Backend Engineer @Cynet
ביום יום מפתח Backend בסביבת SaaS, מיקרו סרביסים בשילוב של מערכות מבוזרות. בזמני הפנוי - לומד ומשתדרג בעולם התוכנה, אוהד מכבי חיפה, פלייסטיישן ובירה עם חברים :)
Linkedin | Twitter

כתיבת תגובה

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