שלום! היום נמשיך ללמוד דפוסי עיצוב ונדבר על שיטת המפעל. תוכלו לגלות במה מדובר ולאילו משימות מתאימה התבנית הזו. נסתכל על דפוס עיצוב זה בפועל ונחקור את המבנה שלו. כדי להבהיר לך את כל האמור לעיל, עליך להבין את הנושאים הבאים:
- ירושה בג'אווה.
- שיטות ושיעורים מופשטים ב-Java.
איזו בעיה פותרת שיטת המפעל?
בכל דפוסי עיצוב המפעל קיימות שתי קבוצות של משתתפים - יוצרים (המפעלים עצמם) ומוצרים (החפצים שיצרו המפעלים). תארו לעצמכם את המצב: יש לנו מפעל שמייצר מכוניות תחת המותג AutoRush. היא יודעת ליצור דגמי מכוניות עם סוגים שונים של גוף:- מכוניות סדאן
- מכוניות סטיישן
- דוּ מוֹשָׁבִית סְגוּרָה
- מכוניות AutoRush
- AutoRush סטיישן
- קופה AutoRush
- מכוניות סדאן של OneAuto
- וואן אוטו סטיישן
- OneAuto קופה
קצת על תבנית המפעל
תן לי להזכיר לך: בנינו איתך בית קפה וירטואלי קטן. בו, בעזרת מפעל פשוט, למדנו איך ליצור סוגים שונים של קפה. היום נחדד את הדוגמה הזו. בואו נזכור איך נראה בית הקפה שלנו עם מפעל פשוט. היה לנו שיעור קפה:public class Coffee {
public void grindCoffee(){
// перемалываем кофе
}
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
וגם כמה מיורשיו - סוגי קפה ספציפיים שהמפעל שלנו יכול לייצר:
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("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
}
מודרניזציה של מפעל פשוט
בית הקפה שלנו מצליח. עד כדי כך שאנחנו חושבים להרחיב. אנחנו רוצים לפתוח כמה נקודות חדשות. בתור חבר'ה יוזמים, לא נוציא בתי קפה מונוטוניים. אני רוצה שלכל אחד יהיה טוויסט משלו. לכן, מלכתחילה, נפתח שתי נקודות: בסגנון איטלקי ואמריקאי. השינויים ישפיעו לא רק על הפנים, אלא גם על המשקאות:- בבית קפה איטלקי נשתמש אך ורק במותגי קפה איטלקיים, עם טחינה וקלייה מיוחדים.
- המנה האמריקאית תהיה קצת יותר גדולה, ובכל הזמנה נגיש מרשמלו מומס - מרשמלו.
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("Вот ваш кофе! Спасибо, приходите еще!");
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) {
/*
Закажем капучино в итальянском стиле:
1. Создадим фабрику для приготовления итальянского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику итальянского кофе
3. Закажем наш кофе
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Закажем капучино в американском стиле
1. Создадим фабрику для приготовления американского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику американского кофе
3. Закажем наш кофе
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
יצרנו שני בתי קפה שונים, והעברנו כל אחד מהם למפעל הנדרש. מצד אחד השגנו את המטרה שלנו, אבל מצד שני... משהו שורט את נשמתו הבלתי ניתנת לריסון של היזם... בואו נבין מה לא בסדר. ראשית, שפע המפעלים. האם ניתן ליצור בכל פעם מפעל משלך לנקודה חדשה ובנוסף לוודא שבעת יצירת בית קפה המפעל הדרוש מועבר לקונסטרוקטור? שנית, זה עדיין מפעל פשוט. רק קצת מודרניזציה. אנחנו עדיין לומדים כאן דפוס חדש. שלישית, האם לא ניתן לעשות זאת אחרת? זה יהיה מגניב אם נוכל למקם את כל השאלות על הכנת קפה בתוך הכיתה CoffeeShop
, לקשר בין תהליכי יצירת הקפה ושירות ההזמנה, אך במקביל לשמור על גמישות מספקת להכנת קפה בסגנונות שונים. התשובה היא כן, אתה יכול. זה נקרא דפוס עיצוב שיטת המפעל.
ממפעל פשוט לשיטת מפעל
כדי לפתור את הבעיה בצורה יעילה ככל האפשר, אנו:- בואו נחזיר את השיטה
createCoffee(CoffeeType type)
למחלקהCoffeeShop
. - בואו נעשה את השיטה הזו למופשטת.
- השיעור עצמו
CoffeeShop
יהפוך למופשט. - לכיתה
CoffeeShop
יהיו יורשים.
CoffeeShop
, מיישם שיטה createCoffee(CoffeeType type)
בהתאם למיטב המסורת של הבריסטות האיטלקיות. אז לפי הסדר. שלב 1. בואו נעשה את הכיתה Coffee
למופשטת. יש לנו כעת שתי משפחות של מוצרים שונים. משקאות קפה איטלקיים ואמריקאים עדיין חולקים אב קדמון משותף: ה Coffee
. נכון יהיה לעשות את זה מופשט:
public abstract class Coffee {
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
שלב 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("Вот ваш кофе! Спасибо, приходите еще!");
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);
}
}
מזל טוב. זה עתה יישמנו את דפוס העיצוב של שיטת המפעל בבית הקפה שלנו.
איך עובדת שיטת המפעל
עכשיו בואו נסתכל מקרוב על מה שקיבלנו. התרשים שלהלן מציג את הכיתות שהתקבלו. בלוקים ירוקים הם כיתות יוצרים, בלוקים כחולים הם כיתות מוצרים. אילו מסקנות ניתן להסיק?- כל המוצרים הם יישומים של המחלקה המופשטת
Coffee
. - כל היוצרים הם מימושים של המחלקה המופשטת
CoffeeShop
. - אנו רואים שתי היררכיות מחלקות מקבילות:
- היררכיה של מוצרים. אנו רואים צאצאים איטלקיים וצאצאים אמריקאים
- היררכיה של יוצרים. אנו רואים צאצאים איטלקיים וצאצאים אמריקאים
- ל- superclass
CoffeeShop
אין מידע לגבי מימוש מוצר ספציפי (Coffee
) שייווצר. - כיתת-על
CoffeeShop
מאצילה את יצירתו של מוצר ספציפי לצאצאיו. - כל כיתת צאצאים
CoffeeShop
מיישמת שיטת מפעלcreateCoffee()
בהתאם לפרטים שלה. במילים אחרות, במסגרת ההטמעות של כיתות יוצרים, מתקבלת החלטה להכין מוצר ספציפי על סמך הפרטים של כיתת היוצר.
מבנה שיטת המפעל
התרשים לעיל מציג את המבנה הכללי של דפוס שיטת המפעל. מה עוד חשוב כאן?- המחלקה Creator מכילה יישומים של כל השיטות המקיימות אינטראקציה עם מוצרים, למעט שיטת המפעל.
- שיטה מופשטת
factoryMethod()
חייבת להיות מיושמת על ידי כל צאצאי המחלקהCreator
. - הכיתה
ConcreteCreator
מיישמת שיטהfactoryMethod()
המייצרת מוצר ישירות. - שיעור זה אחראי ליצירת מוצרים ספציפיים. זהו השיעור היחיד עם מידע על יצירת מוצרים אלה.
- כל המוצרים חייבים ליישם ממשק משותף - להיות צאצאים של מחלקת מוצרים משותפת. זה הכרחי כדי ששיעורים המשתמשים במוצרים יוכלו לפעול עליהם ברמת הפשטות ולא מימושים קונקרטיים.
GO TO FULL VERSION