设计模式之建造者模式——Builder
一、概述
Builder模式,中文名为建造者模式,又名生成器模式、构建者模式等、是创建型设计模式之一。用于将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
1.适用性:
- 对象的创建比较复杂、有多种创建形式时
- 创建复杂对象的算法与对象内部组成和装配是相对独立的
2.UML类图
-
Builder
:定义创建Product
各个部件的抽象接口 -
ConcreteBuilder
:继承自Builder
,实现创建具体类型的Product
所有部件的接口,并提供一个获取最终产品的接口。 -
Director
:借助Builder
,内部封装创建产品的具体流程。
3.UML序列图
- Client创建一个ConcreteBuilder
- Client通过传入ConcreteBuilder创建一个Director
- Client使用Director构造产品
- Client使用ConcreteBuilder组装并获取最终产品
二、实例
想象一下,你作为造物主,你需要创造各种各样的动物,比如狗和猫。查看源码
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;
}
}
- 最后,只剩
Director
了,它来规划Builder要创建哪些东西。
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();
三、变种
实际开发过程中,经常会遇到一种需求:创建对象时,对象的一些部分可以自由选择设置或不设置,但是对象一旦创建完成,便不能对其进行任何修改。例如:ASP.NET Core
框架中WebHost
的创建。接下来,我们来模拟一下女娲造人。
1.同样的,我们先设定好人的基本属性
class Person
{
public string Name { get; set; }
public int Gender { get; set; }
public int Age { get; set; }
}
2.接下来,我们来定义IPersonBuilder
接口,除了Build
方法外,我们都返回了IPersonBuilder
,对于习惯于使用Linq
的.Net开发人员来说,再熟悉不过了,它最大的好处就是能够让我们使用Fluent
的方式来编写代码。
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}");
四、总结
经典版与变种版本的区别:
- 经典版倾向于将构建规划封装起来,Client不关心内部逻辑;而变种版本消除了Director,使得Client拥有更多的主动权,可以自由地进行构建。
- 经典版需要使用Director进行构建,然后使用Builder进行组装返回结果;变种版本则直接使用Builder进行链式构建并组装返回结果,结构更加清晰明了。
- 经典版常用于多种产品的构建是比较固定的情况,Director种类也不宜过多,且必须适应所有产品;而变种版更倾向于某一种产品的构建,构建方式不固定、相当复杂的情况。
如果有新发现会进行补充!