一、概述

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

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组装并获取最终产品

二、实例

想象一下,你作为造物主,你需要创造各种各样的动物,比如狗和猫。查看源码

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要创建哪些东西。

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种类也不宜过多,且必须适应所有产品;而变种版更倾向于某一种产品的构建,构建方式不固定、相当复杂的情况。

查看源码

如果有新发现会进行补充!

设计模式之建造者模式——Builder的更多相关文章

  1. 乐在其中设计模式(C#) - 建造者模式(Builder Pattern)

    原文:乐在其中设计模式(C#) - 建造者模式(Builder Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 建造者模式(Builder Pattern) 作者:webabc ...

  2. 【设计模式】建造者模式 Builder Pattern

    前面学习了简单工厂模式,工厂方法模式以及抽象工厂模式,这些都是创建类的对象所使用的一些常用的方法和套路, 那么如果我们创建一个很复杂的对象可上面的三种方法都不太适合,那么“专业的事交给专业人去做”,2 ...

  3. 二十四种设计模式:建造者模式(Builder Pattern)

    建造者模式(Builder Pattern) 介绍将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 示例用同样的构建过程创建Sql和Xml的Insert()方法和Get()方 ...

  4. 设计模式-05建造者模式(Builder Pattern)

    1.模式动机 比如我们要组装一台电脑,都知道电脑是由 CPU.主板.内存.硬盘.显卡.机箱.显示器.键盘和鼠标组成,其中非常重要的一点就是这些硬件都是可以灵活选择,但是组装步骤都是大同小异(可以组一个 ...

  5. 设计模式之建造者模式(Builder)

    建造者模式原理:建造模式主要是用于产生对象的各个组成部分,而抽象工厂模式则用于产生一系列对象,建造者模式而且要求这些对象的组成部分有序. 代码如下: #include <iostream> ...

  6. 设计模式之建造者模式Builder(创建型)

    1. 概述 在软件开发的过程中,当遇到一个“复杂的对象”的创建工作,该对象由一定各个部分的子对象用一定的算法构成,由于需求的变化,复杂对象的各个部分经常面临剧烈的变化,但将它们组合在一起的算法相对稳定 ...

  7. Python 设计模式之建造者模式 Builder Pattern

    #引入建造者模式 肯德基的菜单上有 薯条, 鸡腿,鸡翅,鸡米花,可乐,橙汁,火腿汉堡,至尊虾汉堡,牛肉汉堡 , 鸡肉卷等这些单品,也有很多套餐. 比如 套餐1:鸡翅,至尊虾汉堡,可乐,薯条 套餐2:鸡 ...

  8. 【UE4 设计模式】建造者模式 Builder Pattern

    概述 描述 建造者模式,又称生成器模式.是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 建造者模式将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无需知道复杂 ...

  9. 创建型设计模式之建造者模式(Builder)

    结构 意图 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 适用性 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时. 当构造过程必须允许被构造的对象有不 ...

随机推荐

  1. 运用session来控制用户的异地登录被挤下线情况

    在用QQ的过程中我们如果你的账号在另外一台手机上面登录,这是腾讯后台会提醒你异地登录,可能你的账号被盗了,然后你手机上得QQ就会被退出登录,这个时候你就需要重新登录修改密码,以确保账号的安全.那这种被 ...

  2. Gridview基础

    gridview是封装好的,直接在设计界面使用,基本不需要写代码 1.绑定数据源 GridView最好与LinQDatasourse配合使用,相匹配绑定数据: 2.外观控制—— 点开有自动套用格式 布 ...

  3. JS中数组的介绍

    一.数组: 一组数据的集合: 二.JS中数组的特点: 1.数组定义时无需指定数据类型: 2.数组定义时可以无需指定数组长度: 3.数组可以存储任何类型的数据: 4.一般是相同的数据类型: 三.数组的创 ...

  4. JAVA设计模式之观察者模式 - Observer

    有趣的事情发生时,可千万别错过了!有一个模式可以帮你的对象知悉现况,不会错过该对象感兴趣的事.对象甚至在运行时可决定是否要继续被通知.有了观察者,你将会消息灵通. 介绍 观察者模式的定义: 在对象之间 ...

  5. 用Excel生成Sql

    用Excel生成Sql: 以如图为例:点击一行数据的后面一个单元格,在上面的fx部分输入=,以等号开头证明这是一个公式.在等号的后面写上想要添加的数据,书写规范是这样:'"&A2&a ...

  6. 解析没有key的Json

    没有key的Json,例如:["http://www.cnblogs.com/Cherry-B/p/4625133.html","http://www.cnblogs.c ...

  7. Mysql如何为表字段添加索引???

    1.添加PRIMARY KEY(主键索引): ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) 2.添加UNIQUE(唯一索引) : ALTE ...

  8. SVN的两种存储方式FSFS和BDB比较【转】

    版本库数据存储 在Subversion1.2中,版本库中存储数据有两种方式.一种是在Berkeley DB数据库中存储数据:另一种是使用普通的文件,使用自定义格式.因为Subversion的开发者称版 ...

  9. 在linux下面安装mysql 确认 配置文件路径 my.cnf

    1.确认服务器my.cnf 文件路径.但不知道那个是 2.通过which mysql命令来查看mysql的安装位置: 3.通过/usr/local/mysql/bin/mysqld --verbose ...

  10. Eclipse Java类编辑器里出现乱码的解决方案

    如图:在Java Class编辑器里出现的这种乱码,非常烦人. 解决方案:Windows->Preference->General->Appearance, 在里面将Theme设置成 ...