33 分鐘閱讀

工廠模式

class Form{
  public Form(){
    if(k == 1){
      Ui ui = new Ui1();
    }
    else if(k == 2){
      Ui ui = new Ui2();
    }
    else{
      Ui ui = new Ui3();
    }

    ui.setPosition(10, 40);
    ui.setColor(10, 40, 210);

    if(k == 1){
      Draw draw = new Draw1();
    }
    else if(k == 2){
      Draw draw = new Draw2();
    }
    else{
      Draw draw = new Draw3();
    }

    draw.draw();
  }
}

上述很亂的程式在產生ui並設定位置
產生工具並畫圖

new 用得好好的,為什麼要使用工廠模式

  1. 耦合高
    在程式裡面有太多實例化,會讓程式的耦合變高
    這個Form還要維護這些物件的實例化
  2. 拒絕修改
    好不容易把這麼亂的程式碼開發完了
    有一天被告知,需要修改功能
    有新的ui要加入
    還是要打開這亂成一坨的程式碼,冒著失誤的風險
  3. 統整性
    如果只有這邊有使用這些程式碼,已經很煩
    如果好多地方都要修改,那一定是一場地獄

最不能接受的還是會一直變動的程式碼
最好的情況就是開發完那就封存
我們先把上面這一份程式碼會變動的部分找出來

封裝起來

// !!這個區域
if(k == 1){
      Ui ui = new Ui1();
}
else if(k == 2){
  Ui ui = new Ui2();
}
else{
  Ui ui = new Ui3();
}
// !!這個區域


ui.setPosition(10, 40);
ui.setColor(10, 40, 210);

上面那段實例化的程式碼是讓我們程式很亂的原因
修改是必須的

上面的Form就是使用者
而且可能有更多使用者
可能你的同事、朋友、還是其他人
每次碰到這段程式碼都要維護很不現實的

簡單工廠模式

public class UiFactory{

  public Ui createUi(int type){
    if(type == 1){
      Ui ui = new Ui1();
    }
    else if(type == 2){
      Ui ui = new Ui2();
    }
    else{
      Ui ui = new Ui3();
    }
    return ui;
  }

}

這樣子就可以先解決眾多使用者們的問題
這個類別也不只只能產生Ui物件
例如有些有多語系的問題
也可以在這邊都弄完
並且幫助用戶把令人繁雜的實例化程式清除了

靜態的簡單工廠

public class UiFactory{

  public static Ui createUi(int type){
    if(type == 1){
      Ui ui = new Ui1();
    }
    else if(type == 2){
      Ui ui = new Ui2();
    }
    else{
      Ui ui = new Ui3();
    }
    return ui;
  }

}

這樣寫也可以
這樣可以確保你在任何時刻都可以直接使用工廠
不用實例化工廠
缺點就是失去了彈性
你無法繼承createUi的行為,做出更符合你想要的行為

簡單工廠的不足之處

簡單工廠非常方便好用,但其實它不是設計模式
只是很直覺常用的寫法

現在的UI不可能只有一種風格
可能有fancy花俏的,古典classical

public class FancyUiFactory{

  public Ui createUi(int type){
    if(type == 1){
      Ui ui = new FancyUi1();
    }
    else if(type == 2){
      Ui ui = new FancyUi2();
    }
    else{
      Ui ui = new FancyUi3();
    }
    return ui;
  }
}
FancyUiFactory fancyUiFactory = new FancyUiFactory();
ClassicalUiFactory classicalUiFactory = new ClassicalUiFactory();

直接分別定義兩個簡單工廠的類別,仔細思考,缺少一種範本Template的感覺

if(uiType == "fancy){
  ui = fancyUiFactory(k);
}
else{
  ui = fancyUiFactory(k);
}
ui.setPosition(10, 40);
ui.setColor(10, 40, 210);
ui.delete();
ui.changeColor(255, 40, 210);

想要標準化這個流程 可以讓不同的Ui風格都可以套用這個template
而且還有一定的流程控管,這樣才可以成為一個稱職的工廠

標準化流程

public abstract class UiFactory{

  public Ui getUi(int type, int[] position, int[] color){
    
    Ui ui = createUi(type);
    ui.setPosition(position);  
    ui.setColor(color);
    return ui;
  }
  abstract Ui createUi(int type);
}

把工廠改成抽象類就可以去執行Template的任務了
因為抽象類不可以實例化,所以你必須去繼承類別

並且createUi 也是抽象方法,為什麼? 可以強迫繼承的子類別去實作這個方法

子類別決定是什麼風格的UI

public class FancyUiFactory extends UiFactory{

  public Ui createUi(int type){
    if(type == 1){
      Ui ui = new FancyUi1();
    }
    else if(type == 2){
      Ui ui = new FancyUi2();
    }
    else{
      Ui ui = new FancyUi3();
    }
    return ui;
  }
}

這樣不管是Fancy還是Classical或其他等等
都可以實作自己的方法,產生相對應風格的UI
並且能維持我們想要的流程

程式如何運行

以基礎類別UiFactory來說
定義了一個createUi的方法

Ui ui = createUi(type); 

這行沒有使用任何具體類別的new去實例化,代表與Ui具體類解耦合
並且多型的接受一個Ui,不會知道是哪一個風格的Ui會被製作

那是在哪時候知道被製作UI的風格的呢?
子類別完成繼承的時候已經決定好了
因為你無法在Fancy的工廠要求產出Classical風格的UI

回頭設計UI

public abstract class UI{
  int width;
  int height;
  Frame frame; //UI外框
  Font font;
  abstract void init();
  void setPosition(int [] position){
    frame.setPosition(position);
  }
  void setColor(int [] color){
    frame.setColor(color);
  }
}
//工廠框架也要修正
public abstract class UiFactory{

  public Ui getUi(int type, int[] position, int[] color){
    Ui ui = createUi(type);
    ui.init()  //初始化UI
    ui.setPosition(position);  
    ui.setColor(color);
    return ui;
  }
  abstract Ui createUi(int type);
}

用抽象方法createUi回傳一個UI變數,讓子類別去實作
使用抽象方法init讓UI類別去實作各自需要的需求 接下來來是設計等待以久的風格UI

public class FancyUi1 extends UI{

  public void init(){
    frame = new FancyFrame();
    font = new FancyFont();
  }
}
// 古典風格的一樣步驟

去實作init得到相對應的frame和font,大功告成

工廠模式介紹

工廠模式包含兩種類型的類別

  1. 建立者(Cretor)
    UiFactory是基礎類別
    有兩個繼承它的子類別,Fancy和Classical
  2. 產品(Product)
    UI是基礎類別 一樣對應健立者的類別
    不過各個類型產品,可以有多個,如FancyBlue,FancyCart

所有工廠模式都會將建立物件的方法封裝起來,透過子類別來實作工廠方法更有彈性

維基百科的解釋;

工廠方法模式(英語:Factory method pattern)是一種實現了「工廠」概念的物件導向設計模式。就像其他建立型模式一樣,
它也是處理在不指定物件具體類型的情況下建立物件的問題。工廠方法模式的實質是「定義一個建立物件的介面,但讓實現這個介
面的類來決定實例化哪個類。工廠方法讓類別的實例化推遲到子類中進行。」

UI產品還是有問題

UI工廠已經是一個很靈活的框架,而且設計良好
但UI產品沒有,因為沒有給它一個明確的規範

public class FancyUi1 extends UI{

  public void init(){
    frame = new FancyFrame();
    font = new FancyFont();
  }
}

上面的程式碼實例化與實體類別耦合
現在希望做的事情
可以讓產品內部的實例化與與產品本身解耦合

思考一下UI產品

這邊有兩個風格的UI,Fancy、Classical
底下各有三個產品
它們應該會有各自的font與frame等等,才可以組成不同的風格
所以用介面把這兩個包裝起來

public interface UiProductFactory{
  Font createFont();
  Frame CreateFrame();

}

組建UI產品的工廠

先實作剛剛準備好的介面,限制類別一定要實作font和frame的方法

public class FancyUIFactory implements UiProductFactory{
  public Font createFont(){
    return new FancyFont();
  }
  public Frame createFrame(){
    return new FancyFrame();
  }
}

與UI產品做結合

現在我們希望做到的事情是可以幫目前的UI產品解耦合

public class FancyUi1 extends UI{

  public void init(){
    frame = new FancyFrame();
    font = new FancyFont();
  }
}

不管是fancy或者Classical風格
唯一個差別就是new frame和new font的類別不一樣而已

可以使用介面統約定這兩個方法

public class FancyUi1 extends UI{
  UiProductFactory uiFactory;
  public FancyUi(UiProductFactory uiFactory){
    this.uiFactory = uiFactory;
  }
  public void init(){
    frame = uiFactory.createFrame();
    font = uiFactory.createFont();
  }
}

如此就可以消除掉剛剛類別與UI實例化的耦合
可以更有彈性了

抽象工廠

剛剛的稱為抽象工廠

參考維基百科的解釋

抽象工廠模式提供了一種方式,可以將一組具有同一主題的單獨的工廠封裝起來。在正常使用中,客戶端程式需要建立抽象工廠的具體實現,
然後使用抽象工廠作為介面來建立這一主題的具體物件。客戶端程式不需要知道(或關心)它從這些內部的工廠方法中獲得物件的具體類型,
因為客戶端程式僅使用這些物件的通用介面。

間單的說提供介面來建立產品的家族
介面就是指這一個

public interface UiProductFactory{
  Font createFont();
  Frame CreateFrame();
}

這樣的設計很好的幫我們解決了耦合的問題,更有彈性
這樣就可以很輕易的替換產品的工廠,產品並不知材料是哪一間工廠提供的
而我們就可以根據不同的狀況使用不同的工廠,來獲得不同的行為了