设计模式之建造者模式——Builder

1、概述

Builder模式,中文名为建造者模式,又名生成器模式、构建者模式等,是建立型设计模式之一。用于将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。git

1.适用性:

  • 对象的建立比较复杂、有多种建立形式时
  • 建立复杂对象的算法与对象内部组成和装配是相对独立的

2.UML类图

  • Builder:定义建立Product各个部件的抽象接口
  • ConcreteBuilder:继承自Builder,实现建立具体类型的Product全部部件的接口,并提供一个获取最终产品的接口。
  • Director:借助Builder,内部封装建立产品的具体流程。

3.UML序列图

  1. Client建立一个ConcreteBuilder
  2. Client经过传入ConcreteBuilder建立一个Director
  3. Client使用Director构造产品
  4. Client使用ConcreteBuilder组装并获取最终产品

2、实例

想象一下,你做为造物主,你须要创造各类各样的动物,好比狗和猫。查看源码

1.在创造前,要先想好狗和猫大致是什么样子,很显然,它们应该有头、身体和脚。算法

abstract class Animal
{
    public string Head { get; set; }
    public string Body { get; set; }
    public string Foots { get; set; }
    
    public abstract void Display();
}

class Dog : Animal
{
    public override void Display()
    {
        Console.WriteLine("-------------- 狗的基本信息 -------------------");
        Console.WriteLine($"头:{Head}");
        Console.WriteLine($"身体:{Body}");
        Console.WriteLine($"脚:{Foots}");
    }
}

class Cat : Animal
{
    public override void Display()
    {
        Console.WriteLine("-------------- 猫的基本信息 -------------------");
        Console.WriteLine($"头:{Head}");
        Console.WriteLine($"身体:{Body}");
        Console.WriteLine($"脚:{Foots}");
    }
}

2.动物的基本特征肯定了,也就明确了要干什么活儿了——建立动物的头、身体和脚。设计模式

interface IAnimalBuilder
{
    void SetHead();
    void SetBody();
    void SetFoots();
}

3.接下来就是考虑狗和猫各部位的建立细节了。不过,别着急编码,先来考虑一件事情:
  DogBuilder里面除了须要实现IAnimalBuilder的全部接口外,还须要提供一个GetDog()的方法,用来把建立好的Dog给外部;一样的CatBuilder中也须要提供一个GetCat()方法。这世间动物有多少种?一千?一万?远远不止!在编码的过程当中很容易就忘记提供这个方法了。因此咱们在抽象Builder和具体Builder中间插入一个抽象层IAnimalBuilder<TAnimal>,全部的ConcreteBuilder都继承自这个接口。框架

interface IAnimalBuilder<TAnimal> : IAnimalBuilder where TAnimal : Animal 
{
    TAnimal GetAnimal();
}

class DogBuilder : IAnimalBuilder<Dog>
{
    private readonly Dog _dog = new Dog();

    public void SetHead()
    {
        _dog.Head = "狗的头";
    }

    public void SetBody()
    {
        _dog.Body = "狗的身体";
    }

    public void SetFoots()
    {
        _dog.Foots = "狗的脚";
    }

    public Dog GetAnimal()
    {
        return _dog;
    }
}

class CatBuilder : IAnimalBuilder<Cat>
{
    private readonly Cat _cat = new Cat();

    public void SetHead()
    {
        _cat.Head = "猫的头";
    }

    public void SetBody()
    {
        _cat.Body = "猫的身体";
    }

    public void SetFoots()
    {
        _cat.Foots = "猫的脚";
    }

    public Cat GetAnimal()
    {
        return _cat;
    }
}

4.最后,只剩Director了,它来规划Builder要建立哪些东西。ide

class Director
{
    private readonly IAnimalBuilder _builder;

    public Director(IAnimalBuilder builder)
    {
        _builder = builder;
    }

    public void Construct()
    {
        _builder.SetHead();
        _builder.SetBody();
        _builder.SetFoots();
    }
}

大功告成!接下来就尝试一下吧测试

var dogBuilder = new DogBuilder();
var dogDirector = new Director(dogBuilder);
dogDirector.Construct();
dogBuilder.GetAnimal().Display();

var catBuilder = new CatBuilder();
var catDirector = new Director(catBuilder);
catDirector.Construct();
catBuilder.GetAnimal().Display();

3、变种

实际开发过程当中,常常会遇到一种需求:建立对象时,对象的一些部分能够自由选择设置或不设置,可是对象一旦建立完成,便不能对其进行任何修改。例如:ASP.NET Core框架中WebHost的建立。接下来,咱们来模拟一下女娲造人。
1.一样的,咱们先设定好人的基本属性ui

class Person
{
    public string Name { get; set; }
    public int Gender { get; set; }
    public int Age { get; set; }
}

2.接下来,咱们来定义IPersonBuilder接口,除了Build方法外,咱们都返回了IPersonBuilder,对于习惯于使用Linq的.Net开发人员来讲,再熟悉不过了,它最大的好处就是可以让咱们使用Fluent的方式来编写代码。this

public interface IPersonBuilder
{
    //注意,这里有问题
    Person Build();
    IPersonBuilder SetName(string name);
    IPersonBuilder SetGender(int gender);
    IPersonBuilder SetAge(int age);
}

你发现什么问题了吗?没错,这违背了面向对象五大原则之一——依赖倒置(抽象不该该依赖具体);并且,这也不知足“对象建立后不得进行更改”的需求。因此,咱们须要提取出一个抽象层IPersion编码

public interface IPerson
{
    string Name { get; }
    int Gender { get; }
    int Age { get; }
}

class Person : IPerson
{
    public string Name { get; set; }
    public int Gender { get; set; }
    public int Age { get; set; }
}

public interface IPersonBuilder
{
    IPerson Build();
    IPersonBuilder SetName(string name);
    IPersonBuilder SetGender(int gender);
    IPersonBuilder SetAge(int age);
}

3.下一步就是构建PersonBuilder了,用来实现IPersonBuilder接口的具体逻辑。设计

class PersonBuilder : IPersonBuilder
{
    private readonly Person _person = new Person();

    public IPerson Build()
    {
        return _person;
    }

    public IPersonBuilder SetName(string name)
    {
        _person.Name = name;
        return this;
    }

    public IPersonBuilder SetGender(int gender)
    {
        _person.Gender = gender;
        return this;
    }

    public IPersonBuilder SetAge(int age)
    {
        _person.Age = age;
        return this;
    }      
}

//提供一个辅助类,用来帮 Client 建立 PersonBuilder
public class PersonBuilderHelper
{
    public static IPersonBuilder CreatePersonBuilder()
    {
        return new PersonBuilder();
    }
}

4.Ok,大功告成!来测试一下吧

var person = PersonBuilderHelper.CreatePersonBuilder()
    .SetAge(20)
    .SetName("jjj")
    .Build();
//输出:jjj,0,20
Console.WriteLine($"{person.Name},{person.Gender},{person.Age}");

4、总结

经典版与变种版本的区别:

  • 经典版倾向于将构建规划封装起来,Client不关心内部逻辑;而变种版本消除了Director,使得Client拥有更多的主动权,能够自由地进行构建。
  • 经典版须要使用Director进行构建,而后使用Builder进行组装返回结果;变种版本则直接使用Builder进行链式构建并组装返回结果,结构更加清晰明了。
  • 经典版经常使用于多种产品的构建是比较固定的状况,Director种类也不宜过多,且必须适应全部产品;而变种版更倾向于某一种产品的构建,构建方式不固定、至关复杂的状况。

查看源码 若是有新发现会进行补充!

相关文章
相关标签/搜索