設計模式學習筆記(八):建造者模式

1 概述

1.1 引言

建造者模式是較爲複雜的建立型模式,它將客戶端與包含多個組成部分(或部件)的複雜對象的建立過程分離,客戶端無須知道複雜對象的內部組成部分與裝配方式,只須要知道所需的建造者類型便可。建造者模式關注一步一步地建立一個複雜對象,不一樣的具體建造者定義了不一樣的建立過程,且具體建造者相互獨立,增長新的建造者很是方便,無須修改已有代碼,系統具備較好的擴展性。java

1.2 複雜對象

建造者模式中用到了複雜對象這個概念。編程

複雜對象就是指那些包含多個成員變量的對象,這些成員變量也叫部件或者零件,例如汽車包括方向盤,發動機,輪胎等 ,
汽車就是複雜對象,方向盤,發動機以及輪胎就是汽車的部件。微信

1.3 定義

建造者模式:將一個複雜的對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。ide

建造者模式是一種對象建立型模式。函數

1.4 結構圖

在這裏插入圖片描述

1.5 角色

建造者模式包含如下四個角色:post

  • Builder(抽象建造者):爲建立一個產品Product對象的各個部件指定抽象接口,在該接口中通常聲明兩類方法,一類是buildXXX()方法,用於建立複雜對象的各個部分(部件),另外一類是是getResult(),用於返回複雜對象。Builder既能夠是抽象類,也能夠是接口
  • ConcreteBuilder(具體建造者):實現了Builder接口或者繼承了Builder類,實現各個部件的具體構造和裝配方法,定義並明確其所建立的複雜對象,也能夠提供一個方法返回建立好的複雜對象
  • Product(產品角色):是被構建的複雜對象,包含多個組成部件,具體建造者建立該產品的內部表示並定義其裝配過程
  • Director(指揮者):指揮者又叫導演類,複雜安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,能夠在其construct()建造方法中調用建造者對象的部件構造以及裝配方法,完成複雜對象的建造。客戶端通常只須要與導演類進行交互,在客戶端肯定具體建造者的類型,並實例化具體建造者對象,而後經過導演類的構造函數或者setter將該對象傳入導演類中

2 典型實現

2.1 步驟

  • 定義產品角色:通常爲複雜對象
  • 定義抽象建造者:根據產品角色肯定bulidXXX的數量,同時聲明相似getResult返回產品角色對象的方法
  • 定義具體建造者:實現抽象建造者中的buildXXX方法
  • 定義導演類:經過構造方法或者setter注入抽象建造者,提供相似construct的方法給外界,調用具體建造者的方法並返回產品角色對象

2.2 產品角色

通常來講Product是一個複雜對象,典型的實現以下:測試

class Product
{
    private type1 part1;
    private type2 part2;
    private type3 part3;
    //getter + setter ...
}

其中type1type2等指各類不一樣的類型,通常來講會有嵌套類。優化

2.3 抽象建造者

抽象建造者的典型實現以下:ui

abstract class Builder
{
    protected Product product = new Product();
    public abstract void buildPart1();
    public abstract void buildPart2();
    public abstract void buildPart3();
    public Product getResult()
    {
        return product;
    }
}

抽象建造者中聲明瞭一系列buildXXX方法,用於建立Product的各個部件,具體建立過程在ConcreteBuilder中實現,getResult()返回已建立完成的Productthis

2.4 具體建造者

ConcreteBuilder實現了Builder中的buildXXX方法,經過調用Product的setter來實現給產品對象的各部分賦值。

不一樣的ConcreteBuilder在實現buildXXX時將有所區別,好比傳入Product的setter參數的不一樣。

另外在有些ConcreteBuilder中某些buildXXX無須實現(提供一個空實現),這些對客戶端來講無須關心,客戶端只須要知道具體建造者的類型便可。

典型實現以下:

class ConcreteBuilder extends Builder
{
    public void buildPart1()
    {
        product.setPart1("part1");
    }
    public void buildPart2()
    {
        product.setPart2("part2");      
    }
    public void buildPart3()
    {
        product.setPart3("part3");
    }
}

2.5 導演類

Director類主要有兩個做用:

  • 隔離了客戶與建立過程
  • 控制產品的建立過程,包括某個buildXXX方法是否被調用,以及調用時的前後次序等等

指揮者針對抽象建造者編程,客戶端只須要知道具體建造者的類型,便可經過指揮者調用建造者的相關方法,返回一個完整的產品對象。典型實現以下:

class Director
{
    private Builder builder;
    public Director(Builder builder)
    {
        this.builder = builder;
    }

    public void setBuilder(Builder builder)
    {
        this.builder = builder;     
    }

    public Product construct()
    {
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
        return builder.getResult();
    }
}

2.6 客戶端

建立具體建造者並傳入導演類做爲構造方法參數,而後調用construct便可獲取產品對象:

public static void main(String[] args) 
{
    Director director = new Director(new ConcreteBuilder());
    Product product = director.construct();
    System.out.println(product.getPart1());
}

3 實例

遊戲角色的建立:不一樣的角色具備差異極大的外部特徵,並且要求隨着遊戲的進行會不斷出現新的角色,也就是說擴展性要好。這裏例子簡化就建立三個角色:英雄,天使與惡魔,使用建造者模式進行設計。

設計以下:

  • 產品對象:Actor
  • 抽象建造者:ActorBuilder
  • 具體建造者:HeroBuilder+AngelBuilder+DevilBuilder
  • 指揮者:ActorController

代碼以下:

// 複雜產品
class Actor
{
    private String type;
    private String face;
    private String costume;
    private String hairstyle;
    //getter and setter ...
}

//抽象建造者
abstract class ActorBuilder
{
    protected Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public Actor createActor()
    {
        return actor;
    }
}

//具體建造者
class HeroBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("英雄"); }
    public void buildFace(){ actor.setFace("英俊"); }
    public void buildCostume(){ actor.setCostume("盔甲"); }
    public void buildHairstyle(){ actor.setHairstyle("飄逸"); }
}

class AngleBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("天使"); }
    public void buildFace(){ actor.setFace("漂亮"); }
    public void buildCostume(){ actor.setCostume("白裙"); }
    public void buildHairstyle(){ actor.setHairstyle("披肩長髮"); }
}

class DevilBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("惡魔"); }
    public void buildFace(){ actor.setFace("帥氣"); }
    public void buildCostume(){ actor.setCostume("黑衣"); }
    public void buildHairstyle(){ actor.setHairstyle("紅色"); }
}

// 指揮者類
class ActorController
{
    public Actor construct(ActorBuilder builder)
    {
        builder.buildType();
        builder.buildFace();
        builder.buildHairstyle();
        builder.buildCostume();
        return builder.createActor();
    }
}

測試類:

public class Test
{
    public static void main(String[] args) {
        ActorBuilder builder = new AngleBuilder();
        ActorController controller = new ActorController();
        Actor actor = controller.construct(builder);
        System.out.println(actor.getType());
        System.out.println(actor.getCostume());
        System.out.println(actor.getHairstyle());
        System.out.println(actor.getFace());
    }
}

4 優化

4.1 省略Director

其實Director是能夠省略的,直接與Builder合併,在Builder中提供相似Direcotr中的construct()方法,並定義爲靜態方法,如:

abstract class ActorBuilder
{
    protected static Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public static Actor build(ActorBuilder builder)
    {
        builder.buildType();
        builder.buildFace();
        builder.buildHairstyle();
        builder.buildCostume();
        return actor;
    }
}

同時客戶端代碼修改以下:

Actor actor = ActorBuilder.build(new AngleBuilder());
//Actor actor = ActorBuilder.build(new HeroBuilder());
//Actor actor = ActorBuilder.build(new DevilBuilder());

再簡單一點的能夠省略createActor中的參數:

abstract class ActorBuilder
{
    protected Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public Actor build()
    {
        buildType();
        buildFace();
        buildHairstyle();
        buildCostume();
        return actor;
    }
}

同時客戶端簡化以下:

Actor actor = new AngleBuilder().build();

這兩種方式簡化了系統結構的同時又不影響靈活性以及可擴展性,可是加劇了抽象建造者的職責,若是build方法較爲複雜,待構建的產品組成部分較多,建議仍是將其單獨封裝在Director中,這樣更加符合SRP(單一權責原則)。

4.2 鉤子方法

鉤子方法是一種能夠控制是否調用某個buildXXX的方法,特徵以下:

  • 返回類型爲boolean
  • 方法名通常爲isXXX

例如修改ActorBuilder以下:

abstract class ActorBuilder
{
    protected Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public boolean isBareheaded()
    {
        return false;
    }

    public Actor createActor()
    {
        return actor;
    }
}

並修改DevilBuilder,覆蓋默認方法:

class DevilBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("惡魔"); }
    public void buildFace(){ actor.setFace("帥氣"); }
    public void buildCostume(){ actor.setCostume("黑衣"); }
    public void buildHairstyle(){ actor.setHairstyle("紅色"); }
    public boolean isBareheaded(){ return true; }
}

最後修改ActorController

class ActorController
{
    public Actor construct(ActorBuilder builder)
    {
        builder.buildType();
        builder.buildFace();
        builder.buildCostume();
        if(builder.isBareheaded())
            builder.buildHairstyle();
        return builder.createActor();
    }
}

相比起以前的ActorController多了一次判斷,測試以下:

public static void main(String[] args) {
    ActorController controller = new ActorController();
    Actor actor = controller.construct(new AngleBuilder());
    System.out.println(actor.getType());
    System.out.println(actor.getCostume());
    System.out.println(actor.getHairstyle());
    System.out.println(actor.getFace());
    System.out.println();

    actor = controller.construct(new DevilBuilder());
    System.out.println(actor.getType());
    System.out.println(actor.getCostume());
    System.out.println(actor.getHairstyle());
    System.out.println(actor.getFace());
}

輸出以下:

在這裏插入圖片描述

4.3 返回Builder

在實際應用中Director較少出現,一般只有Builder以及Product,並且Builder是做爲Product的內部類,提供一系列set方法,這些set方法返回一個Builder方便後續調用,最後以一個build()結尾,好比OkHttp中的Request/OkHttpClient

OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5000,TimeUnit.MILLISECONDS)
.readTimeout(10,TimeUnit.SECONDS)
.build();

Request request = new Request.Builder()
.url("https://xxx")
.post(requestBody)
.build();

5 主要優勢

  • 封裝細節:建造者模式中客戶端不須要知道產品內部組成細節,將產品自己與產品的建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象
  • 擴展性好:每個具體建造者都相對獨立,而與其餘建造者無關,伊尼茨能夠很方便地替換具體建造者或增長新的具體建造者,用戶使用不一樣的具體建造者便可獲得不一樣的產品對象。因爲指揮者針對抽象建造者編程,增長新的具體建造者無須修改原有類庫的代碼,擴展方便,符合開閉原則
  • 控制建立過程:將複雜產品的建立步驟分解在不一樣的方法中,使得建立過程更加清晰,更加精細地控制建立過程

6 主要缺點

  • 範圍受限:建造者模式所建立的產品通常具備較多的共同點,其組成部分類似,若是產品之間的差別性很大,例如不少組成部分不相同,就不適合使用建造者模式,所以使用範圍收到必定限制
  • 建造者多:若是產品內部結構複雜多變,可能會須要定義不少具體建造者類來實現這種變化,增大系統的理解難度與運行成本

7 適用場景

  • 須要生成的產品對象有複雜的內部結構,這些產品對象一般包含多個成員變量
  • 須要生成的產品對象的屬性相互依賴,須要指定其生成順序
  • 對象的建立過程獨立於建立該對象的類。在建造者模式中經過引入指揮者類,將建立過程封裝在指揮者類中,而再也不建造者類或者客戶類中
  • 隔離複雜對象的建立與使用,並使得相同的建立過程能夠建立不一樣的產品

8 總結

在這裏插入圖片描述

若是以爲文章好看,歡迎點贊。

同時歡迎關注微信公衆號:氷泠之路。

在這裏插入圖片描述

相關文章
相關標籤/搜索