設計模式-建造者模式

煮詩君發表於2020-08-25

闊別已久了的部落格,我又回來。為什麼又好長時間沒寫了,因為最近產品給我的需求稍微有點多,加上自己給自己加的需求,導致經常早九晚二,沒空輸出了。最近我司專案加入了多渠道快速打包和熱更新(老專案新增新技術真的是困難重重,花費了較長時間),並研究了下 Jenkins 持續整合打包(但是由於公司伺服器不給我用,先割了段時間)。

最近在專案中寫bug的時候發現,專案中的公共Dialog管理類是這樣的:

設計模式-建造者模式

導致每一次增加一種型別的 Dialog 就要增加一個建構函式來實現,久而久之這個類屬性也越來越多,構造引數的種類也越來越多,這個類的初衷是為了封裝系統AlterDialog的使用,讓一些相同樣式的Dialog可以用最簡單的樣式建立,無奈UI樣式隨著產品的迭代,越來越多,這種單純的封裝有點讓人力不從心了。

本文將介紹一種簡單的設計模式「建造者模式」。也許它可以很好的解決生產中類似上述描述的情況。

建造者模式的組成部分

我所理解的設計模式思想,本身就是用於解決重複程式碼封裝的一種思想。是前輩們給我們留下的經驗。而「書本中的建造者模式」是下邊的下邊這樣子的:

設計模式-建造者模式

可以看出設計模式由四部分組成:

  1. Product: 產品類所描述的是我們最終想要的到的結果統稱,例如一個封裝好的帶有多選按鈕列表的 Dialog。
  2. Builder: 建造者的介面定義,他定義了建造某種產品類需要的步驟,但不提供實現。
  3. ConcreteBuilder: 建造者的實現類,繼承自 Builder 提供一種建立型別的具體步驟,是我們封裝的擴充物件,比如專案之前只有雙按鈕的對話方塊,現在我們有一種帶多選功能的對畫框需要建立,我們就可以新建一個 ConcreteBuilder 繼承自Builder,來完成對多選框Dialog的建立工作,而無序改動前兩者。
  4. Director: Builder 類的建立者,由他來為各種 Builder 來賦值屬性,Director隔離了建造者實現類與實際客戶端建立,讓實際物件的建立只關注於物件的屬性。

建造者模式的例子

下面通過一個抽象派的 Dota 英雄建立過程,來看下上述四部分是怎麼工作的:

首先定一個 DotaHero 類這個類是我們最終的產品類也就是 Product,他可以理解為一個超類,具體實現決定於建造者如何建造。

public class DotaHero {
    //英雄分類 智力 力量 和 敏捷
    public static final int HERO_INTELLECTUAL = 1;
    public static final int HERO_POWEER = 2;
    public static final int HERO_AGLIE = 3;
    @IntDef(value = {HERO_AGLIE, HERO_POWEER, HERO_INTELLECTUAL})
    public @interface HeroType {
    }
    //英雄屬性
    private String heroName;
    private String heroDes;
    private int heroType;
    private HashMap<String, String> heroSkills;

    //施放置頂技能
    public void executeSkill(String key) {
        if (heroSkills.containsKey(key)) {
            System.out.println("施放 " + key + " 技能: " + heroSkills.get(key));
        }
    }
    ..... //省略很多的get set
}
複製程式碼

DotaHero 是建造者建造的目標,這裡定義的屬性和方法是最全的,不同的建造者,通過不同的構建步驟,為屬性賦值得到最終的不同英雄。

下面我們來定一個一個抽象的建造者建立類,該類定義了所有建造者需要的屬性,實現類可以任意擴充套件。

public abstract class HeroBuilder {
    //最終建造的物件
    protected DotaHero hero;
    
    public HeroBuilder() {
        //這裡在建構函式中就初始化了物件,其實最好的方法是將其放到build方法中。
        hero = new DotaHero();
    }

    
    public HeroBuilder setHeroName(String name) {
        hero.setHeroName(name);
        return this;
    }

    public HeroBuilder setHeroDes(String des) {
        hero.setHeroDes(des);
        return this;
    }


    public HeroBuilder addHeroSkill(String keyName, String skill) {
        hero.addHeroSkill(keyName, skill);
        return this;
    }
    
    //定義了英雄型別的抽象方法,需要子類去實現
    protected abstract HeroBuilder setHeroType();
    
    public DotaHero build() {
        return hero;
    }
}
複製程式碼

接下來我們來定義Dota中的英雄建造者,大家都知道,Dota 中英雄有 智力,敏捷,力量三種,所以我們這裡定義三種建造者用來構建者三種型別的英雄。

//智力
public class AglieHeroBuilder extends HeroBuilder {
    @Override
    protected HeroBuilder setHeroType() {
        hero.setHeroType(DotaHero.HERO_AGLIE);
        return this;
    }
}
//敏捷
public class IntellectualHeroBuilder extends HeroBuilder {
    @Override
    protected HeroBuilder setHeroType() {
        hero.setHeroType(DotaHero.HERO_INTELLECTUAL);
        return this;
    }
}
//力量
public class PowerHeroBuilder extends HeroBuilder {
    @Override
    protected HeroBuilder setHeroType() {
        hero.setHeroType(DotaHero.HERO_POWEER);
        return this;
    }
}
複製程式碼

上述列舉了三種建造者,這裡只做了抽象方法的實現,實際生產中,子類可以根據需要擴充獨有屬性。

完成上述工作後,我們就已經能夠使用建造者來建立我們需要的物件了。但建造者模式中推薦我們將實際物件的生產的工作不要暴露給客戶端,需要通過建立者(建立建造者的類)來隔離建造者和客戶端,這樣客戶端只需要宣告我需要一個什麼樣的建造者,來建立一個什麼樣的物件就可以了。這樣當以後出現了新的建造者型別的時候,客戶端仍舊只需要關注上述兩點。

下面我們來看下建立者的定義:

public class Director {

    private HeroBuilder builder;
    //建構函式1 直接在外界定義好某型別的 Builder 傳入
    public Director(@NonNull HeroBuilder builder) {
        this.builder = builder;
    }

    public DotaHero getAHero() {
        if (builder != null) {
            return builder.build();
        }
        return null;
    }
}
複製程式碼

上述程式碼暴露的個構造方法來建立建造者。他目前所做的工作就是獲取建造者或者根據型別建立建造者,通過getAHero方法來獲得一個生產物件。

何時該應用建造者模式

通過上述的例子我們可以很好的理解,第一部分建造者模式的各組成部分的作用以及意義。那麼在實際生產過程中我們何時該應用建造者模式呢?

在以下情況下可以使用建造者模式:

  • 需要生成的產品物件有複雜的內部結構,這些產品物件通常包含多個成員屬性。
  • 需要生成的產品物件的屬性相互依賴,需要指定其生成順序。
  • 建造者物件的建立過程獨立於建立該物件的類。在建造者模式中引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類中。
  • 隔離複雜物件的建立和使用,並使得相同的建立過程可以建立不同的產品。

建造者模式的意義在於,建造者模式是一步一步建立一個複雜的物件,它允許使用者只通過指定複雜物件的型別和內容就可以構建它們,使用者不需要知道內部的具體構建細節。

##建造者模式的優缺點

建造者模式的優點:

  1. 在建造者模式中, 客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。

  2. 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者, 使用者使用不同的具體建造者即可得到不同的產品物件 。

  3. 增加新的具體建造者無須修改原有類庫的程式碼,建立者類針對抽象建造者類程式設計,系統擴充套件方便,符合“開閉原則”。

建造者模式的缺點:

  1. 建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制。
  2. 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,會導致具體建造者數量變得很多。

總結

通過上文的敘述相信大家已經知道什麼是建造者模式了,我們也清楚了建造者模式定義步驟,但是實際開發中,如果一個產品類,變化的範圍僅僅是屬性不同或者構造順序不同,那麼一個建造者實現類就可以完成工作,那麼我們可以省略後兩個角色,也就是說 Builder 的類兼職完成步驟2和步驟3。

學習設計模式,筆者認為最好的學習方法就是應用,本文中列舉的例子可能過於簡單,離掌握還差的遠,下一片文章將會帶領大家瞭解我對我司專案中 Dialog 建立封裝的過程,從實踐中來理解。

參考連結:

建造者模式

設計模式之嬋 第2版

相關文章