Scala 與設計模式(四):Factory 工廠模式

在中國歷史上,房子經常與當下同樣稀缺,住房問題一樣是一個讓百姓苦惱的社會熱點。git

在拆違章建築還不盛行的年代,咱們能夠選擇在深山老林裏本身修建住所。在 Java 中多是這樣實現的:github

class BuildingA {
    private String name;

    public BuildingA(String name) {
        this.name = name;
    }

    public void build() {
        System.out.println(name + " is building");
    }
}

class BuildingB {
    private String name;

    public BuildingB(String name) {
        this.name = name;
    }

    public void build() {
        System.out.println(name + " is building");
    }
}

// 使用
  BuildingA buildingA = new BuildingA("bedroom");
  BuildingB buildingB = new BuildingB("kitchen");
  buildingA.build();
  buildingB.build();複製代碼

村裏的牛大哥在建完兩間房子以後,後知後覺:本身想要的房間格局不一樣,可是風格得相同,能夠把公共的部分抽離出來:設計模式

interface IBuilding {
    void build();
}

abstract class AbstractBuilding implements IBuilding {
    protected void buildCommon(){
        System.out.println("Europe style"); // 公共的部分
    }
}

 class BuildingAs extends AbstractBuilding {
    private String name;
    public BuildingAs(String name){
        this.name = name;
    }
    @Override
    public void build() {
        this.buildCommon();
        System.out.println(name + " is building");
    }
}

 class BuildingBs extends AbstractBuilding {
    private String name;
    public BuildingBs(String name) {
        this.name = name;
    }

    @Override
    public void build() {
        this.buildCommon();
        System.out.println(name + " is building");
    }
}

// 使用
BuildingAs buildingA = new BuildingAs("bedroom");
BuildingBs buildingB = new BuildingBs("kitchen");
buildingA.build();
buildingB.build();複製代碼

可是這麼作以後,牛大哥發如今建造的時候並無省力,他向村口的王師傅請教,爲何我考慮了不少反而沒什麼做用呢?app

簡單工廠模式

王師傅告訴他:雖然你找出了一些公共的流程,但在實際建造過程當中,你仍是完整的過了全部的流程(構造方法不一樣,每次都要 new 對象)。另外,ide

另外,你對房屋的需求並很少,因此優點不夠明顯。函數

說着掏出一個寶盒,盒子裏有不少設計圖:下次你能夠委託我來造一些組件(再也不須要本身 new):測試

public class SimpleFactory {
    public static IBuilding getProduct(String name){
        if("bedroom".equals(name)){
            return new BuildingA(name);
        }else if("kitchen".equals(name)){
            return new BuildingB(name);
        }else{
            throw new IllegalArgumentException();
        }
    }
}

// 使用
IBuilding buildingA =  SimpleFactory.getProduct("bedroom");
IBuilding buildingB =  SimpleFactory.getProduct("kitchen");
buildingA.build();
buildingB.build();複製代碼

王師傅幫助下的牛大哥在後面的建造中感受輕鬆多了。ui

這就是「簡單工廠模式」,也稱做「靜態工廠方法模式」。this

優勢

它有如下幾個優勢:spa

  • 簡化對象建立的 API
  • 減小 new 關鍵字對代碼的干擾
  • 代碼更精簡優雅

而牛二哥明顯沒有那麼幸運,他的妻子追求個性,而且很善變,老是在建造過程當中更改需求。

缺點

雖然牛二哥也去王師傅那獲取組件,每次王師傅都要拿出他的寶盒,在裏面翻一遍,再告訴牛二哥 —— 這個我不會造。站在 OCP(開放封閉原則)的角度講,該模式的擴展不夠良好,每次有新的模型後都要修改工廠。

工廠方法模式

老王師傅也經不起折騰,想着不能閉關鎖國,就把本身會建造的組件貼在顯眼的地方,有新的組件直接加在上面就好:

interface IFactory {
    public IBuilding createBuilding();
}

class FactoryA implements IFactory{

    @Override
    public IBuilding createBuilding() {
      // 能夠進行復雜的處理,每一種方法對應一種模型
        return new BuildingA("bedroom");
    }
}

class FactoryB implements IFactory{

    @Override
    public IBuilding createBuilding() {
        return new BuildingA("kitchen");
    }
}

class FactoryC implements IFactory{

    @Override
    public IBuilding createBuilding() {
        return new BuildingA("restroom");
    }
}

// 使用
FactoryA factoryA = new FactoryA();
FactoryB factoryB = new FactoryB();
FactoryC factoryC = new FactoryC();
factoryA.createBuilding();
factoryB.createBuilding();
factoryC.createBuilding();複製代碼

這樣你們的溝通是方便了不少,並且老王也不用每次都搜一遍傳家寶盒。

這種模式被 GOF 稱做「工廠方法模式」。

定義

工廠方法模式(Factory Method Pattern)是一種實現了「工廠」概念的面向對象設計模式。就像其餘建立型模式同樣,它也是 處理在不指定對象具體類型的狀況 下建立對象的問題。定義以下:

定義一個用於建立對象的接口,讓子類決定實例化哪個類。Factory Method 使一個類的實例化延遲到其子類。 — 《設計模式》 GOF

從以上也可看出:工廠作的事很簡單 —— 封裝內部的實現細節

優勢

它能夠帶來如下好處:

  1. 下降耦合度。在工廠方法模式中,工廠方法用來建立用戶所須要的產品,同時還向用戶隱藏了哪一種具體產品類將被實例化這一細節,用戶只須要關心所需產品對應的工廠,無須關心建立細節,甚至無須知道具體產品類的類名。
  2. 良好擴展性。須要新的產品類型時,只須要新增一個工廠類,不須要更改產品以及產品的接口以及用戶的使用方式。
  3. 代碼結構清晰。用戶使用時不須要構造內部結構,直接調用工廠方法便可。

缺點

咱們可能會趕上如下問題:

  1. 增長使用成本。使用工廠模式的時候通常都會採用接口或者抽象類,有時還會涉及到反射、DOM 等方式。
  2. 增長系統複雜度(影響不顯著)。一個工廠類對應一個產品,因此增長產品類的時候就須要增長工廠類。

工廠方法模式針對的是一個產品等級結構,當要處理多個產品等級結構時(ex. 創建不一樣小區,小區裏有不一樣樓宇,樓裏還有不一樣戶型),咱們不但願對每一個模型都創建一個工廠,這太糟糕了,來看看「抽象工廠模式」是如何解決的。

抽象工廠模式

定義

爲建立一組相關或相互依賴的對象提供一個接口,並且無需指定他們的具體類。

咱們也可把「一組相關或相互依賴的對象」稱做「產品族」。

利用抽象工廠,咱們能夠這麼寫:

interface IBuildingA {
    void buildA();
}

interface IBuildingB {
    void buildB();
}

interface IFactory {
    public IBuildingA createBuildingA();
    public IBuildingB createBuildingB();
}

class BuildingA implements IBuildingA {
    ... // 省略構造函數
    @Override
    public void buildA() {
        System.out.println((name + "is building"));
    }
}

class BuildingB implements IBuildingB {
    ... // 省略構造函數
    @Override
    public void buildB() {
        System.out.println(name + " is building");
    }
}

class Factory implements IFactory{
    @Override
    public IBuildingA createBuildingA() {
        return new BuildingA("big bedroom");
    }

    @Override
    public IBuildingB createBuildingB() {
        return  new BuildingB("small bedroom");
    }
}

// 測試
Factory factory = new Factory();
factory.createBuildingA();
factory.createBuildingB();複製代碼

咱們能夠直接在一個工廠類中實現多個方法,這樣不用管理多個工廠,使用和管理起來都更方便。

若是說工廠方法解決問題的方式是「廣搜」,那抽象工廠亦可看做「深搜」。

總結

以上,咱們使用到了三種設計模式:簡單工廠(靜態工廠方法)工廠方法抽象工廠
在三種模式中,咱們要作的都是將工廠的初始化與構造分離

雖然比起直接 new 要增長很多代碼,但在後期維護的時候,能給咱們提供不少的便利。

看完 Java 版本,咱們再來看看 Scala 是如何實現的。

Scala 實現

在 Scala 中,依舊能夠用相似 Java 的方式來實現,只用把 Java 中的關鍵字 interface 換成 trait 便可,直接看代碼吧。

簡單工廠模式

trait IBuilding {
  def show()
}

case class SimpleBuilding(name: String)extends IBuilding {
  def show = println("SimpleBuilding " + name + " is building")
}

case class LuxuryBuilding(name: String) extends IBuilding{
  def show = println("LuxuryBuilding " + name + " is building")
}

object ConstructionFactory {
  def createBuilding(kind: String): IBuilding =  kind match {
    case "Simple" =>   SimpleBuilding("Simple")
    case "Luxury" =>   LuxuryBuilding("Luxury")
  }
}

object Test extends App {
  val simpleBuilding: IBuilding = ConstructionFactory.createBuilding("Simple")
  val luxuryBuilding: IBuilding = ConstructionFactory.createBuilding("Luxury")
  simpleBuilding.show()
  luxuryBuilding.show()
}複製代碼

除了這種方式,Scala 還爲咱們提供了一種相似構造器的語法 —— apply,經過這種方式,咱們能夠省略工廠類,只需增長產品類接口的伴生對象:

object IBuilding {
  def apply(kind: String): IBuilding = kind match {
    case "Simple" =>   SimpleBuilding("Simple")
    case "Luxury" =>   LuxuryBuilding("Luxury")
  }
}複製代碼

調用者有更好的體驗:

val simpleBuilding: IBuilding = IBuilding("Simple")
val luxuryBuilding: IBuilding = IBuilding("Luxury")
simpleBuilding.show()
luxuryBuilding.show()複製代碼

嚴格意義講,這種方法並不屬於 GOF 提到的工廠方法,它缺乏了工廠模塊,咱們能夠稱之爲「靜態工廠模式」。

工廠方法與抽象工廠的實現與 Java 相似,代碼就不貼出來了。不瞭解 Scala 的同窗能夠參考源碼

總結

內部組成

以上,不難總結出工廠模式中的四種角色(簡單工廠模式中沒有抽象工廠):

  • 抽象產品:它是定義產品的接口,是工廠方法模式所建立對象的超類型,也就是產品對象的公共父類。(ex. 文中 IBuiiding)。
  • 具體產品:它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠建立,具體工廠和具體產品之間一一對應。(ex. 文中 Buiiding)
  • 抽象工廠: 在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,全部建立對象的工廠類都必須實現該接口。(ex. 文中 IFactory)
  • 具體工廠: 它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。(ex. 文中 ConstructionFactory)

適用場景

固然,咱們不能爲了設計而設計,當類結構簡單的時候,咱們能夠直接使用 new 來創造,不然會增長沒必要要的代碼,反而使結構複雜化。

全部工廠模式適用場景相似:調用者無需知道他所使用的對象的類(實際上內部結構對調用者是透明的 ex. 簡單工廠)。

但仍是有所差別,如下爲我的理解:

名稱 適用場景
簡單工廠 1. 工廠類負責建立的對象比較少
2. 客戶只知道傳入工廠類的參數,對於如何建立對象(邏輯)不關心
工廠方法 工廠類負責建立的對象複雜, 且內部對象層級關係比較簡單
抽象工廠 工廠類負責建立的對象複雜, 且內部對象層級關係比較複雜

源碼連接

若有錯誤和講述不恰當的地方還請指出,不勝感激!

相關文章
相關標籤/搜索