一起學習設計模式--06.建造者模式

前言

沒有人買車會只買一個輪胎或一個方向盤,大家買的都是一輛包含輪胎、方向盤和發動機等多個部件的完整汽車。如何將這些部件組裝成一輛完整的汽車並返回給用戶,這是建造者模式需要解決的問題。

建造者模式又稱生成器模式,它是一種較爲複雜、使用頻率也相對較低的創建型模式。建造者模式向客戶端返回的不是一個簡單的產品,而是一個由多個部件組成的複雜產品。

一、遊戲角色設計

A公司遊戲開發小組決定開發一款名爲《xx羣俠傳》的網絡遊戲,該遊戲採用主流的RPG模式。玩家可以在遊戲中扮演虛擬世界中的一個特定角色,角色根據不同的遊戲情節和統計數據(如力量、魔法、技能等)具有不同的能力,角色也會隨着不斷升級而擁有更加強大的能力。

作爲 RPG 遊戲的一個重要組成部分,需要對遊戲角色進行設計,而且隨着該遊戲的升級將不斷增加新的角色。不同類型的遊戲角色,其性別、臉型、服裝、髮型等外部特性都有所差異,例如“天使”擁有美麗的面容和披肩長髮,並且穿一身白裙;而“惡魔”極其醜陋,留着光頭並穿一件刺眼的黑衣。

A 公司決定開發一個小工具來創建遊戲角色,可以創建不同類型的角色並可以靈活的增加新的角色。

開發人員通過分析發現,遊戲角色是一個複雜對象,它包含性別、臉型等多個組成部分,不同的遊戲角色其組成部分有所差異。

但是,無論何種造型的遊戲角色,他們的創建步驟都大同小異,都需要逐步創建其組成部分,再將各部分裝配成一個完整的遊戲角。如何一步一步的創建一個包含多個組成部分的複雜對象,建造者模式爲解決此類問題而誕生。

二、建造者模式概述

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

建造者模式(Builder Pattern):將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。建造者模式是一種對象創建型模式。

建造者模式結構如圖:

在建造者模式結構圖中包含以下4個角色:

  1. Builder(抽象建造者):它爲創建一個產品 Product 對象的各個部件指定抽象接口,在該接口中一般聲明兩類方法:一類方法是 BuildPartX(),用於創建複雜對象的各個部件;另一類方法是 GetResult() ,用於返回複雜對象。Builder 既可以是抽象類,也可以是接口。
  2. ConcreteBuilder(具體建造者):它實現了 Builder 接口,實現各個部件的具體構造和裝配方法,定義並明確其所創建的複雜對象,也可以提供一個方法返回創建好的複雜產品對象。
  3. Product(產品角色):它是被構建的複雜對象,包含多個組成部件。具體建造者創建該產品的內部表示並定義其裝配過程。
  4. Director(指揮者):它負責安排複雜對象的建造次序。指揮者與抽象建造者之間存在關聯關係,可以在其 Construct() 方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端一般只需要與指揮者進行交互,在客戶端確定具體建造者的類型,並實例化具體建造者對象(也可以通過配置文件或反射機制),然後通過指揮者的構造函數或Setter方法將該對象傳入指揮者類中。

什麼是複雜對象?簡單的說,複雜對象是指那些包含多個成員變量的對象,這些成員變量也稱爲部件或零件。典型複雜對象代碼如下:

public class Product {
 	public string PartA { get; set; }
    private string PartB { get; set; }
    private string PartC { get; set; }
}

抽象建造者類中定義了產品的創建方法和返回方法,典型代碼如下:

public abstract Builder {
    //創建產品對象
 	protected Product product = new Product();
    
    public abstract void BuildPartA();
    public abstract void BuildPartB();
    public abstract void BuildPartC();
    
    //返回產品對象
    public Product GetResult() {
     	return product;   
    }
}

指揮者類 Director ,該類主要有兩個作用:一方面它隔離了客戶與創建過程,另一方面它控制產品的創建過程。指揮者針對抽象建造者編程,客戶端只需要知道具體建造者的類型,即可通過指揮者類調用建造者的相關方法,返回一個完整的產品對象。
指揮者類的代碼如下:

public class Director {
 	private Builder _builder;
    
    public Director (Builder builder){
     	_builder = builder;
    }
    
    public void SetBuilder(Builder builder){
     	_builder = builder;
    }
    
    //產品構建和組裝方法
    public Product Construct() {
     	_builder.BuildPartA();
        _builder.BuildPartB();
        _builder.BuildPartC();
        return _builder.GetResult();
    }
}

對於客戶端來說,只需關心具體的建造者即可。代碼如下:

...
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.Construct();
...

可以通過配置文件來存儲具體建造者類的類名,使得更換新的建造者時,無需修改源代碼,系統擴展更方便。

三、完整解決方案

開發人員決定使用建造者模式來實現遊戲角色的創建,基本結構如下:

  • ActorController :充當指揮者
  • ActorBuilder :充當抽象建造者
  • HeroBuilder、AngelBuilder、DevilBuilder充當具體建造者
  • Actor:充當複雜產品

完整代碼如下:

    /// <summary>
    /// 角色類:複雜產品
    /// </summary>
    public class Actor
    {
        /// <summary>
        /// 角色類型
        /// </summary>
        public string Type { get; set; }
        /// <summary>
        /// 性別
        /// </summary>
        public string Sex { get; set; }
        /// <summary>
        /// 臉型
        /// </summary>
        public string Face { get; set; }
        /// <summary>
        /// 服裝
        /// </summary>
        public string Costume { get; set; }
        /// <summary>
        /// 髮型
        /// </summary>
        public string Hairstyle { get; set; }
    }

    /// <summary>
    /// 角色建造器:抽象建造者
    /// </summary>
    public abstract class ActorBuilder
    {
        protected Actor actor = new Actor();

        public abstract void BuildType();
        public abstract void BuildSex();
        public abstract void BuildFace();
        public abstract void BuildCostume();
        public abstract void BuildHairstyle();

        /// <summary>
        /// 工廠方法,返回一個完整的遊戲角色對象
        /// </summary>
        /// <returns></returns>
        public Actor CreateActor()
        {
            return actor;
        }
    }

    /// <summary>
    /// 英雄角色建造器:具體建造者
    /// </summary>
    public class HeroBuilder : ActorBuilder
    {
        public override void BuildType()
        {
            actor.Type = "英雄";
        }

        public override void BuildSex()
        {
            actor.Sex = "男";
        }

        public override void BuildFace()
        {
            actor.Face = "英俊";
        }

        public override void BuildCostume()
        {
            actor.Costume = "盔甲";
        }

        public override void BuildHairstyle()
        {
            actor.Hairstyle = "飄逸";
        }
    }
    
    /// <summary>
    /// 天使角色建造器:具體建造者
    /// </summary>
    public class AngelBuilder : ActorBuilder
    {
        public override void BuildType()
        {
            actor.Type = "天使";
        }

        public override void BuildSex()
        {
            actor.Sex = "女";
        }

        public override void BuildFace()
        {
            actor.Face = "漂亮";
        }

        public override void BuildCostume()
        {
            actor.Costume = "白裙";
        }

        public override void BuildHairstyle()
        {
            actor.Hairstyle = "披肩長髮";
        }
    }
    
    /// <summary>
    /// 惡魔角色建造器:具體建造者
    /// </summary>
    public class DevilBuilder : ActorBuilder
    {
        public override void BuildType()
        {
            actor.Type = "惡魔";
        }

        public override void BuildSex()
        {
            actor.Sex = "妖";
        }

        public override void BuildFace()
        {
            actor.Face = "醜陋";
        }

        public override void BuildCostume()
        {
            actor.Costume = "黑衣";
        }

        public override void BuildHairstyle()
        {
            actor.Hairstyle = "光頭";
        }
    }
    
    /// <summary>
    /// 遊戲角色創建控制器:指揮者
    /// </summary>
    public class ActorController
    {
        /// <summary>
        /// 逐步構建複雜產品對象
        /// </summary>
        /// <param name="ab"></param>
        /// <returns></returns>
        public Actor Construct(ActorBuilder ab)
        {
            ab.BuildType();
            ab.BuildSex();
            ab.BuildFace();
            ab.BuildCostume();
            ab.BuildHairstyle();

            var actor = ab.CreateActor();
            return actor;
        }
    }

將具體的建造者寫在配置文件中,通過反射來創建:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ConcreteBuilder" value="LXP.DesignPattern.Builder.AngelBuilder"/>
  </appSettings>
</configuration>
    /// <summary>
    /// 配置文件幫助類
    /// </summary>
    public static class AppConfigHelper
    {
        public static object GetBuilder()
        {
            try
            {
                var concreteBuilder = ConfigurationManager.AppSettings["ConcreteBuilder"];
                var type = Type.GetType(concreteBuilder);

                return type == null ? null : Activator.CreateInstance(type);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            return null;
        }
    }

客戶端代碼:

    class Program
    {
        static void Main(string[] args)
        {
            var ab = AppConfigHelper.GetBuilder() as ActorBuilder;
            var controller=new ActorController();
            var actor = controller.Construct(ab);

            Console.WriteLine($"{actor.Type}的外觀");
            Console.WriteLine($"性別:{actor.Sex}");
            Console.WriteLine($"面容:{actor.Face}");
            Console.WriteLine($"服裝:{actor.Costume}");
            Console.WriteLine($"發行:{actor.Hairstyle}");
        }
    }

編譯並輸出結果:

建造者模式中,客戶端只需要實例化指揮者類,指揮者類針對抽象建造者編程。客戶端根據實際需要傳入具體的建造者類型,指揮者將指導具體建造者一步一步的構造一個完整的產品,相同的構建過程可以創建完全不同的產品。如果需要更換角色,只需要修改配置文件,更換具體的角色建造者類即可,如果要新增角色,可以新增一個具體的角色建造者類作爲抽象角色建造者的子類,再修改配置文件即可,原有代碼無須修改,符合開閉原則。

四、建造者模式總結

建造者模式的核心在於如何一步一步的構建一個包含多個組成部件的完整對象,使用相同的構建過程構建不同的產品。在軟件開發中,如果需要創建複雜對象,並希望系統具備很好的靈活性和可擴展性,可以考慮使用建造者模式。

1.主要優點

  1. 在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建出不同的產品對象。
  2. 每個具體的建造者都相對獨立,而與其它具體建造者無關。因此,可以很方便的替換具體建造者或增加新的具體建造者。由於指揮者針對抽象建造者編程,增加新的具體建造者無須修改原有類庫的代碼,系統擴展更加方便,符合開閉原則。
  3. 可以更加精細的控制產品的創建過程。將複雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更加方便使用程序來控制創建過程。

2.主要缺點

  1. 建造者模式所創建的產品一般都具有較多的共同點,其組成部分相似。如果產品之間的差異性很大,例如很多組成部分都不相同,就不適合使用建造者模式,因此其使用範圍受到一定的限制。
  2. 如果產品的內部結構複雜且多變,可能會需要定義很多具體建造者來實現這種變化,這就導致系統變的很龐大,增加系統的理解難度和運維技術。

3.使用場景

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

如果您覺得這篇文章有幫助到你,歡迎推薦,也歡迎關注我的公衆號。

示例代碼:

https://github.com/crazyliuxp/DesignPattern.Simples.CSharp

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章