藉由定義一個class來負責建立其他class的instance,被建立的instance通常都具有共同的super class
這段話是什麼意思呢?首先我們先來看一個簡單了例子:
我們開了一間餐廳(Restaurant.class),只賣了牛排。客人來點了一份牛排,做法如下:
class Restaurant{
public Steak cookSteak(){
Steak steak = new Steak()
steak.cook();
steak.delivery();
return steak
}
}
public class Steak{
public void cook(){
System.out.println("把牛排煮熟");
};
public void delivery(){
System.out.println("送牛排");
};
}
這樣一來,客人就可以來餐廳吃牛排了。由於只有牛排已經無法滿足客人的需求,所以老闆決定,菜單上面加上烤雞供客人選擇:
class Restaurant{
public Steak cookSteak(){
Steak steak = new Steak()
steak.cook();
steak.delivery();
return steak;
}
public Chicken cookChicken(){
Chicken chicken = new Chicken();
chicken.cook();
chicken.delivery();
return chicken
}
}
public class Steak{
public void cook(){
System.out.println("把牛排煮熟");
};
public void delivery(){
System.out.println("送牛排");
};
}
public class Chicken{
public void cook(){
System.out.println("把雞肉煮熟");
};
public void delivery(){
System.out.println("送雞肉");
};
}
這樣就餐廳就有牛排跟烤雞可以販售了。可以發現不管是牛排還是烤雞,都需要進行cook以及delivery兩道手續,所以我們可以將這兩種方法放到interface內封裝起來,讓Chicken和Steak去實作:
public interface CookMeal{
void cook();
void delivery();
}
class Steak implements CookMeal{
@Override
public void cook(){
System.out.println("把牛排煮熟");
};
@Override
public void delivery(){
System.out.println("送牛排");
};
}
class Chicken implements CookMeal{
@Override
public void cook(){
System.out.println("把雞肉煮熟");
};
@Override
public void delivery(){
System.out.println("送雞肉");
};
}
如此一來,我們就能確保每道料理的工序是一樣的不會出錯。接著回到餐廳的部分,現在只有兩道餐提供給客人選擇,今天老闆想要新增一到豬排,我們可以這樣寫:
// ...Steak 略
// ...Chicken 略
class Pork implements CookMeal{
@Override
public void cook(){
System.out.println("把豬排煮熟");
};
@Override
public void delivery(){
System.out.println("送豬排");
};
}
class Restaurant{
public Steak cookSteak(){
Steak steak = new Steak()
steak.cook();
steak.delivery();
return steak;
}
public Chicken cookChicken(){
Chicken chicken = new Chicken();
chicken.cook();
chicken.delivery();
return chicken
}
public Pork cookPork(){
Pork pork = new Pork();
pork.cook();
pork.delivery();
return pork
}
}
這樣菜單上就有豬排可以選擇了。不過隨著餐廳的營運,菜單上的餐點會不段的增加,這樣的寫法並不是一個好的方法,所以餐廳的點餐邏輯做了一些更動:
class Restaurant{
CookMeal mealOrder(String mealType){
CookMeal meal;
if ("Steak".equals(mealType)){
meal = new Steak();
} else if ("Chicken".equals(mealType)){
meal = new Chicken();
} else if ("Pork".equals(mealType)){
meal = new Pork();
}
meal.cook();
meal.delivery();
return meal;
}
}
前面提到Steak, Chicken, Pork都實作CookMeal,所以這三個項目合成為一個判斷邏輯,在if-else內判斷客人要點哪種餐點,這樣未來菜單再增加時,就不需要再寫一大串在程式內了。
但問題來了!如果我們不在自己的店裡生產食物原料怎麼辦?又或是別家店也想要食物原料怎麼辦?
註:根據OO的開放封閉原則,我們不能在這個地方做修改,詳情可以點這裡
在現實生活中,餐廳會跟肉廠叫肉,肉送到餐廳後再進行烹煮。這邊也是一樣,我們將建立食材物件的邏輯額外拉出來,取名叫做mealFactory,這裡面就是放置上述if-else的邏輯:
class MealFactory{
public CookMeal createMeal(String mealType){
CookMeal meal = null;
if (mealType.equals("Steak")){
meal = new Steak();
} else if (mealType.equals("Chicken")){
meal = new Chicken();
} else if (mealType.equals("Pork")){
meal = new Pork();
}
return meal;
}
}
mealFactory我們稱做factory class,裡面有一個createMeal的方法,由這個方法去決定產出哪些食材,也就是說統一在這個工廠內生產食材物件。
接著因為餐廳不在由自己產生食材,變成統一由工廠送來餐廳,所以我們逮來整裡餐廳(Restaurant.class):
class Restaurant{
private MealFactory factory;
public Restaurant (MealFactory factory){
this.factory = factory;
}
public CookMeal mealOrder(String mealType){
CookMeal meal;
meal = factory.createMeal(mealType);
meal.cook();
meal.delivery();
return meal;
}
}
factory就是餐廳與工廠合作的管道,只要將指定的食材名稱傳入工廠的createMeal,工廠就會生產食材物件給餐廳,供餐廳烹煮,如同我們一開始講的:
藉由定義一個class來負責建立其他class的instance,被建立的instance通常都具有共同的super class
經過上述的例子,相信大家對sample factory pattern有了初步的認識,接下來我們來看一下UML來比較有用pattern以及沒有用pattern的差異。
由UML圖可以得知,一開始的Restaurant與Steak及Chicken是緊緊相連的,若我們要增加餐點,相依性會越來越高,對於整體開發來說是不理想的。
我們將cook以及delivery封裝到CookMeal供Chicken和Steak實作,然後生產食材的部份交給MealFactory負責,Restaurant最後只需要跟工廠叫貨回來烹煮就好。如此一來就算需要增加菜單,也只需要新增工廠的邏輯,餐廳本身並不會受到影響,也降低了類別間的耦合。
接著我們可以試著點餐看看!!
public class Order {
public static void main(String args[]) {
Restaurant r = new Restaurant(new MealFactory());
r.mealOrder("Steak");
r.mealOrder("Chicken");
}
}
output :
把牛排煮熟
送牛排
把雞肉煮熟
送雞肉
可以從下表得知在上述範例中,每個類別組成所對應的角色及功能。
角色 | 名稱 | 功能 |
---|---|---|
Factory(工廠) | MealFactory | 實現建立物件(食材)的邏輯 |
Product(抽象產品) | CookMeal | 定義抽象方法供ConcreteProduct實作 |
ConcreteProduct(實體產品) | Steak, Chicken, Pork | 實作產品的商業邏輯 |
以上就是Sample Factory Pattern的介紹,不過Sample Factory Pattern不算Design Pattern的一種,它比較偏向開發習慣。雖然有許多優點,但Sample Factory Pattern其實也存在著一些問題,例如:若商品一直增加,會導致集中在factory的邏輯變得複雜,會難以維護。
而接下來要介紹的Factory Method Pattern就是Design Pattern中的其中一種,可以解決上述遇到的問題。
定義了一個建立物件的insterface,由子類別決定實體化的類別為何者。
這是Factory Method Pattern的目標。那要如何實現呢?我們來接續著看!
剛剛有提到Sample Factory Pattern的組成角色及功能。Factory Method Pattern當然也有:
角色 | 名稱 | 功能 |
---|---|---|
Factory(抽象工廠) | MealFactory | 定義抽象方法供ConcreteFactory實作 |
ConcreteFactory(實體工廠) | SteakFactory, ChickenFactory, PorkFactory | 實作Factory並建立物件 |
Product(抽象產品) | CookMeal | 定義抽象方法供ConcreteProduct實作 |
ConcreteProduct(實體產品) | Steak, Chicken, Pork | 實作產品的商業邏輯 |
由上表組成中發現Factory不再只有一個,而是變成了一個抽象一個實體。為什麼呢?剛才有講到Sample Factory Pattern,若商品一直增加,會導致集中在factory的邏輯變得複雜,會難以維護。所以需要將Factory的部分也做拆分。
這是Sample Factory Pattern的例子:
class Restaurant{
private MealFactory factory;
public Restaurant (MealFactory factory){
this.factory = factory;
}
public CookMeal mealOrder(String mealType){
CookMeal meal;
meal = factory.createMeal(mealType);
meal.cook();
meal.delivery();
return meal;
}
}
class MealFactory{
public CookMeal createMeal(String mealType){
CookMeal meal = null;
if (mealType.equals("Steak")){
meal = new Steak();
} else if (mealType.equals("Chicken")){
meal = new Chicken();
} else if (mealType.equals("Pork")){
meal = new Pork();
}
return meal;
}
}
由於我們不想要在MealFactory無止盡的增加邏輯,所以我們建立一個將MealFactory改成interface,並將裡面生產食材的邏輯分別抽出實作。好比原本是一家大的肉廠,同時處理各種肉品,但隨著品項越來越多,大肉廠沒辦法負荷,所以成立子公司,有牛肉公司、豬肉公司...等等。分別讓子公司處理對應的食材,總公司只要把SOP制定好讓子公司照著做即可。
interface MealFactory{
public CookMeal createMeal();
}
class SteakFactory implements MealFactory{
public CookMeal createMeal(){
return new Steak();
}
}
class ChickenFactory implements MealFactory{
public CookMeal createMeal(){
return new Chicken();
}
}
class PorkFactory implements MealFactory{
public CookMeal createMeal(){
return new Pork();
}
}
各類肉工廠就這樣建立起來了,若未來我們還需要一個羊肉工廠,那就再建立一個羊肉的sub class即可,不在需要到MealFactory增加邏輯,造成MealFactory複雜度上升。
那我們實際來跑跑程式!!
class Restaurant{
private MealFactory factory;
public Restaurant (MealFactory factory){
this.factory = factory;
}
public CookMeal mealOrder(){
CookMeal meal;
meal = factory.createMeal();
meal.cook();
meal.delivery();
return meal;
}
}
public class Order {
public static void main(String args[]) {
Restaurant rSteak = new Restaurant(new SteakFactory());
rSteak.mealOrder();
Restaurant rChicken = new Restaurant(new ChickenFactory());
rChicken.mealOrder();
}
}
output
把牛排煮熟
送牛排
把雞肉煮熟
送雞肉
Order內的寫法稍作改變,記得Factory Method Pattern開頭所說的定義了一個建立物件的insterface,由子類別決定實體化的類別為何者。
,意思就是我們在需要的時候再建立需要的物件。
現在,我們再來看看UML圖:
可以看到各種肉類工廠實作了MealFactory,如此一來建立食材物件就交由個肉類工廠做處理,MealFactory不在需要進行複雜的邏輯判斷。而如果想要新增不同品項的產品,直接加入
ConcreteFactory(實體工廠)以及ConcreteProduct(實體產品)即可。
這篇我們介紹了Sample Factory Pattern 以及 Factory Method Pattern,可以在這做一個整理:
Pattern | Sample Factory | Factory Method |
---|---|---|
優點 | 1. 由factory建立物件,client可以免除。2. 可以減少使用者的記憶量。3. 可以在不修改client資料的情況下,變更ConcreteProduct,提高系統靈活性。 | 1. 建立物件只需要知道名稱,不需要知道細節 2. 擴展性加 |
缺點 | 1. factory若有問題便無法使用。 2. 邏輯集中在factory,產品過多會導致邏輯複雜且難以維護。 | 產品數量增加,伴隨著系統複雜度的增加 |
Sample Factory Pattern :
藉由定義一個class來負責建立其他class的instance,被建立的instance通常都具有共同的super class
Factory Method Pattern:
定義了一個建立物件的insterface,由子類別決定實體化的類別為何者。
範例1:Sample Factory Pattern
範例2:Factory Method Pattern
簡單工廠模式 SimpleFactor
工廠模式| 菜鳥教程
工廠設計模式 (Factory Design Pattern)