CodeGym /בלוג Java /Random-HE /דפוסי עיצוב: שיטת המפעל
John Squirrels
רָמָה
San Francisco

דפוסי עיצוב: שיטת המפעל

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

איזו בעיה פותרת שיטת המפעל?

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

קצת על דפוס המפעל

הרשו לי להזכיר לכם שבעבר בנינו בית קפה וירטואלי קטן. בעזרת מפעל פשוט למדנו איך ליצור סוגים שונים של קפה. היום נעבוד מחדש את הדוגמה הזו. בואו ניזכר איך נראה בית הקפה שלנו, עם המפעל הפשוט שלו. היה לנו שיעור קפה:
public class Coffee {
    public void grindCoffee(){
        // Grind the coffee
    }
    public void makeCoffee(){
        // Brew the coffee
    }
    public void pourIntoCup(){
        // Pour into a cup
    }
}
וכמה כיתות ילדים התואמות לסוגים ספציפיים של קפה שהמפעל שלנו יכול לייצר:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
יצרנו רשימה כדי להקל על ביצוע הזמנות:
public enum CoffeeType {
    ESPRESSO,
    AMERICANO,
    CAFFE_LATTE,
    CAPPUCCINO
}
מפעל הקפה עצמו נראה כך:
public class SimpleCoffeeFactory {
    public Coffee createCoffee(CoffeeType type) {
        Coffee coffee = null;

        switch (type) {
            case AMERICANO:
                coffee = new Americano();
                break;
            case ESPRESSO:
                coffee = new Espresso();
                break;
            case CAPPUCCINO:
                coffee = new Cappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new CaffeLatte();
                break;
        }

        return coffee;
    }
}
ולבסוף, בית הקפה עצמו נראה כך:
public class CoffeeShop {

    private final SimpleCoffeeFactory coffeeFactory;

    public CoffeeShop(SimpleCoffeeFactory coffeeFactory) {
        this.coffeeFactory = coffeeFactory;
    }

    public Coffee orderCoffee(CoffeeType type) {
        Coffee coffee = coffeeFactory.createCoffee(type);
        coffee.grindCoffee();
        coffee.makeCoffee();
        coffee.pourIntoCup();

        System.out.println("Here's your coffee! Thanks! Come again!");
        return coffee;
    }
}

מודרניזציה של מפעל פשוט

בית הקפה שלנו פועל היטב. עד כדי כך שאנחנו שוקלים להרחיב. אנחנו רוצים לפתוח כמה מקומות חדשים. אנחנו נועזים ויוזמים, אז לא נוציא בתי קפה משעממים. אנחנו רוצים שלכל חנות יהיה טוויסט מיוחד. בהתאם לכך, מלכתחילה, נפתח שני מיקומים: אחד איטלקי ואחד אמריקאי. שינויים אלה ישפיעו לא רק על עיצוב הפנים, אלא גם על המשקאות המוצעים:
  • בבית הקפה האיטלקי, נשתמש אך ורק במותגי קפה איטלקיים, עם טחינה וקלייה מיוחדים.
  • במיקום האמריקאי יהיו מנות גדולות יותר, ואנו נגיש מרשמלו עם כל הזמנה.
הדבר היחיד שנותר ללא שינוי הוא המודל העסקי שלנו, שהוכיח את עצמו כמצוין. מבחינת הקוד, זה מה שקורה. היו לנו 4 שיעורים התואמים למוצרים שלנו:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
אבל עכשיו יהיו לנו 8:
public class ItalianStyleAmericano extends Coffee {}
public class ItalianStyleCappucino extends Coffee {}
public class ItalianStyleCaffeLatte extends Coffee {}
public class ItalianStyleEspresso extends Coffee {}

public class AmericanStyleAmericano extends Coffee {}
public class AmericanStyleCappucino extends Coffee {}
public class AmericanStyleCaffeLatte extends Coffee {}
public class AmericanStyleEspresso extends Coffee {}
מכיוון שאנו רוצים לשמור על המודל העסקי הנוכחי, אנו רוצים שהשיטה orderCoffee(CoffeeType type)תעבור כמה שפחות שינויים. תסתכל על זה:
public Coffee orderCoffee(CoffeeType type) {
    Coffee coffee = coffeeFactory.createCoffee(type);
    coffee.grindCoffee();
    coffee.makeCoffee();
    coffee.pourIntoCup();

    System.out.println("Here's your coffee! Thanks! Come again!");
    return coffee;
}
אילו אפשרויות עומדות בפנינו? ובכן, אנחנו כבר יודעים איך לכתוב מפעל, נכון? הדבר הפשוט ביותר שעולה מיד בראש הוא לכתוב שני מפעלים דומים, ולאחר מכן להעביר את היישום הרצוי לבנאי בית הקפה שלנו. בכך, המעמד של בית הקפה לא ישתנה. ראשית, עלינו ליצור מחלקת מפעל חדשה, לגרום לה לרשת את המפעל הפשוט שלנו, ולאחר מכן לעקוף את createCoffee(CoffeeType type)השיטה. בואו נכתוב מפעלים ליצירת קפה בסגנון איטלקי וקפה בסגנון אמריקאי:
public class SimpleItalianCoffeeFactory extends SimpleCoffeeFactory {

    @Override
    public Coffee createCoffee(CoffeeType type) {
        Coffee coffee = null;
        switch (type) {
            case AMERICANO:
                coffee = new ItalianStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new ItalianStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new ItalianStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new ItalianStyleCaffeLatte();
                break;
        }
        return coffee;
    }
}

public class SimpleAmericanCoffeeFactory extends SimpleCoffeeFactory{

    @Override
    public Coffee createCoffee (CoffeeType type) {
        Coffee coffee = null;

        switch (type) {
            case AMERICANO:
                coffee = new AmericanStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new AmericanStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new AmericanStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new AmericanStyleCaffeLatte();
                break;
        }

        return coffee;
    }

}
כעת נוכל להעביר ל-CoffeeShop את היישום הרצוי במפעל. בואו נראה איך ייראה הקוד להזמנת קפה מבתי קפה שונים. לדוגמה, קפוצ'ינו בסגנון איטלקי ואמריקאי:
public class Main {
    public static void main(String[] args) {
        /*
            Order an Italian-style cappuccino:
            1. Create a factory for making Italian coffee
            2. Create a new coffee shop, passing the Italian coffee factory to it through the constructor
            3. Order our coffee
         */
        SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
        CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
        italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);


         /*
            Order an American-style cappuccino
            1. Create a factory for making American coffee
            2. Create a new coffee shop, passing the American coffee factory to it through the constructor
            3. Order our coffee
         */
        SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
        CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
        americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
    }
}
יצרנו שני בתי קפה שונים, והעברנו לכל אחד את המפעל הרצוי. מצד אחד, הגשנו את המטרה שלנו, אבל מצד שני... איכשהו זה לא מסתדר עם היזמים... בואו נבין מה לא בסדר. ראשית, שפע המפעלים. מה? כעת לכל מיקום חדש, אנו אמורים להקים מפעל משלו ובנוסף לכך, לדאוג שהמפעל הרלוונטי יועבר לקונסטרוקטור בעת יצירת בית קפה? שנית, זה עדיין מפעל פשוט. פשוט עבר מודרניזציה קלה. אבל אנחנו כאן כדי ללמוד דפוס חדש. שלישית, האם אין אפשרות לגישה אחרת? זה יהיה נהדר אם נוכל להכניס את כל הנושאים הקשורים להכנת קפה לכיתה CoffeeShopעל ידי קישור בין תהליכי יצירת הקפה והזמנות שירות, ובו זמנית לשמור על גמישות מספקת להכנת סגנונות קפה שונים. התשובה היא כן, אנחנו יכולים. זה נקרא דפוס עיצוב שיטת המפעל.

ממפעל פשוט לשיטת מפעל

כדי לפתור את המשימה בצורה יעילה ככל האפשר:
  1. אנחנו מחזירים את createCoffee(CoffeeType type)השיטה לכיתה CoffeeShop.
  2. נהפוך את השיטה הזו למופשטת.
  3. השיעור CoffeeShopעצמו יהפוך למופשט.
  4. בכיתה CoffeeShopיהיו כיתות ילדים.
כן חבר. בית הקפה האיטלקי הוא לא יותר מצאצאי הכיתה CoffeeShop, המיישמת את createCoffee(CoffeeType type)השיטה בהתאם למיטב המסורת של הבריסטות האיטלקיות. עכשיו, צעד אחד בכל פעם. שלב 1. הפוך את Coffeeהכיתה למופשט. יש לנו שתי משפחות שלמות של מוצרים שונים. ובכל זאת, לקפה האיטלקי והאמריקאי יש אב קדמון משותף - המעמד Coffee. זה יהיה נכון לעשות את זה מופשט:
public abstract class Coffee {
    public void makeCoffee(){
        // Brew the coffee
    }
    public void pourIntoCup(){
        // Pour into a cup
    }
}
שלב 2. צור מופשט, בשיטה CoffeeShopמופשטתcreateCoffee(CoffeeType type)
public abstract class CoffeeShop {

    public Coffee orderCoffee(CoffeeType type) {
        Coffee coffee = createCoffee(type);

        coffee.makeCoffee();
        coffee.pourIntoCup();

        System.out.println("Here's your coffee! Thanks! Come again!");
        return coffee;
    }

    protected abstract Coffee createCoffee(CoffeeType type);
}
שלב 3. צור בית קפה איטלקי, שהוא צאצא של בית הקפה המופשט. אנו מיישמים את createCoffee(CoffeeType type)השיטה בה, תוך התחשבות בפרט של מתכונים איטלקיים.
public class ItalianCoffeeShop extends CoffeeShop {

    @Override
    public Coffee createCoffee (CoffeeType type) {
        Coffee coffee = null;
        switch (type) {
            case AMERICANO:
                coffee = new ItalianStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new ItalianStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new ItalianStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new ItalianStyleCaffeLatte();
                break;
        }
        return coffee;
    }
}
שלב 4. אנחנו עושים את אותו הדבר עבור בית הקפה בסגנון אמריקאי
public class AmericanCoffeeShop extends CoffeeShop {
    @Override
    public Coffee createCoffee(CoffeeType type) {
        Coffee coffee = null;

        switch (type) {
            case AMERICANO:
                coffee = new AmericanStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new AmericanStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new AmericanStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new AmericanStyleCaffeLatte();
                break;
        }

        return coffee;
    }
}
שלב 5. בדוק כיצד ייראה לאטה אמריקאי ואיטלקי:
public class Main {
    public static void main(String[] args) {
        CoffeeShop italianCoffeeShop = new ItalianCoffeeShop();
        italianCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);

        CoffeeShop americanCoffeeShop = new AmericanCoffeeShop();
        americanCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
    }
}
מזל טוב. זה עתה יישמנו את דפוס העיצוב של שיטת המפעל תוך שימוש בבית הקפה שלנו כדוגמה.

העיקרון מאחורי שיטות המפעל

עכשיו בואו נשקול בפירוט רב יותר מה קיבלנו. התרשים שלהלן מציג את הכיתות שהתקבלו. הבלוקים הירוקים הם מחלקות יוצרים, והבלוקים הכחולים הם מחלקות מוצרים. דפוסי עיצוב: שיטת המפעל - 2אילו מסקנות נוכל להגיע?
  1. כל המוצרים הם יישומים של Coffeeהמחלקה המופשטת.
  2. כל היוצרים הם מימושים של CoffeeShopהמחלקה המופשטת.
  3. אנו רואים שתי היררכיות מחלקות מקבילות:
    • היררכיה של מוצרים. אנו רואים צאצאים איטלקיים וצאצאים אמריקאים
    • היררכיה של יוצרים. אנו רואים צאצאים איטלקיים וצאצאים אמריקאים
  4. למחלקת CoffeeShopהעל אין מידע לגבי איזה מוצר ספציפי ( Coffee) ייווצר.
  5. מעמד CoffeeShopהעל מאציל את יצירתו של מוצר ספציפי לצאצאיו.
  6. כל צאצא של CoffeeShopהכיתה מיישם createCoffee()שיטת מפעל בהתאם למאפיינים הספציפיים שלו. במילים אחרות, ההטמעות של מחלקות היצרנים מכינות מוצרים ספציפיים על סמך הפרטים של מחלקת היצרן.
כעת אתה מוכן להגדרה של תבנית שיטת המפעל . תבנית שיטת המפעל מגדירה ממשק ליצירת אובייקט, אך מאפשרת לתת-מחלקות לבחור את המחלקה של האובייקט שנוצר. לפיכך, שיטת מפעל מאצילה יצירת מופע לתת-מחלקות. באופן כללי, זכירת ההגדרה אינה חשובה כמו להבין איך הכל עובד.

מבנה של שיטת מפעל

דפוסי עיצוב: שיטת המפעל - 3התרשים לעיל מציג את המבנה הכללי של דפוס שיטת המפעל. מה עוד חשוב כאן?
  1. המחלקה Creator מיישמת את כל השיטות המקיימות אינטראקציה עם מוצרים, מלבד שיטת המפעל.
  2. השיטה המופשטת factoryMethod()חייבת להיות מיושמת על ידי כל צאצאי הכיתה Creator.
  3. הכיתה ConcreteCreatorמיישמת את factoryMethod()השיטה, שיוצרת ישירות את המוצר.
  4. שיעור זה אחראי ליצירת מוצרים ספציפיים. זהו השיעור היחיד עם מידע על יצירת מוצרים אלה.
  5. כל המוצרים חייבים ליישם ממשק משותף, כלומר הם חייבים להיות צאצאים של מחלקת מוצרים משותפת. זה הכרחי כדי ששיעורים המשתמשים במוצרים יוכלו לפעול עליהם כהפשטות, ולא כיישום ספציפי.

שיעורי בית

היום עשינו די הרבה עבודה ולמדנו את דפוס העיצוב של שיטת המפעל. הגיע הזמן לחזק את החומר! תרגיל 1. עשה את העבודה כדי לפתוח בית קפה נוסף. זה יכול להיות בית קפה בסגנון אנגלי או ספרדי. או אפילו בסגנון חללית. הוסף צבעי מאכל לקפה כדי לגרום לו לזהור, והקפה שלך פשוט לא מהעולם הזה! תרגיל 2. בשיעור האחרון היה לך תרגיל שבו יצרת סושי בר וירטואלי או פיצרייה וירטואלית. עכשיו התרגיל שלך הוא לא לעמוד במקום. היום למדת איך להשתמש בתבנית שיטת המפעל לטובתך. זה הזמן להשתמש בידע הזה ולהרחיב את העסק שלך ;)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION