iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 11
0

本文同步分享於個人blog

  • Sample Factory Pattern


定義

藉由定義一個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的開放封閉原則,我們不能在這個地方做修改,詳情可以點這裡

Sample Factory Pattern 實作

在現實生活中,餐廳會跟肉廠叫肉,肉送到餐廳後再進行烹煮。這邊也是一樣,我們將建立食材物件的邏輯額外拉出來,取名叫做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的差異。

SP1

由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 :

把牛排煮熟
送牛排
把雞肉煮熟
送雞肉

Sample Factory Pattern 成員

可以從下表得知在上述範例中,每個類別組成所對應的角色及功能。

角色 名稱 功能
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中的其中一種,可以解決上述遇到的問題。

  • Factory Method Pattern


定義

定義了一個建立物件的insterface,由子類別決定實體化的類別為何者。

這是Factory Method Pattern的目標。那要如何實現呢?我們來接續著看!

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制定好讓子公司照著做即可。

Factory Method Pattern 實作

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圖:

FM

可以看到各種肉類工廠實作了MealFactory,如此一來建立食材物件就交由個肉類工廠做處理,MealFactory不在需要進行複雜的邏輯判斷。而如果想要新增不同品項的產品,直接加入
ConcreteFactory(實體工廠)以及ConcreteProduct(實體產品)即可。

  • 小結


這篇我們介紹了Sample Factory Pattern 以及 Factory Method Pattern,可以在這做一個整理:

兩種Pattern的優缺點
Pattern Sample Factory Factory Method
優點 1. 由factory建立物件,client可以免除。2. 可以減少使用者的記憶量。3. 可以在不修改client資料的情況下,變更ConcreteProduct,提高系統靈活性。 1. 建立物件只需要知道名稱,不需要知道細節 2. 擴展性加
缺點 1. factory若有問題便無法使用。 2. 邏輯集中在factory,產品過多會導致邏輯複雜且難以維護。 產品數量增加,伴隨著系統複雜度的增加
Factory Pattern的目標

Sample Factory Pattern :

藉由定義一個class來負責建立其他class的instance,被建立的instance通常都具有共同的super class

Factory Method Pattern:

定義了一個建立物件的insterface,由子類別決定實體化的類別為何者。

  • 範例程式碼


範例1:Sample Factory Pattern
範例2:Factory Method Pattern

  • References


簡單工廠模式 SimpleFactor
工廠模式| 菜鳥教程
工廠設計模式 (Factory Design Pattern)


上一篇
[Day10] 單例模式 | Singleton Pattern
下一篇
[Day12] 抽象工廠模式 | Abstract Factory Pattern
系列文
從生活中認識Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言