Builder生成器模式
一、概述
一、概述
在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?这就是要说的建造者模式。
建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。
对象性质的建造
有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等部分,而在最起码的收件人地址未被赋值之前,这个电子邮件不能发出。
有些情况下,一个对象的一些性质必须按照某个顺序赋值才有意义。在某个性质没有赋值之前,另一个性质则无法赋值。这些情况使得性质本身的建造涉及到复杂的商业逻辑。
这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的零件,建造产品的过程就是组合零件的过程。由于组合零件的过程很复杂,因此,这些"零件"的组合过程往往被"外部化"到一个称作建造者的对象里,建造者返还给客户端的是一个全部零件都建造完毕的产品对象。
之所以使用"建造者"而没有用"生成器"就是因为用零件生产产品,"建造"更为合适,"创建"或"生成"不太恰当。
二、意图
将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
三、Builder模式的结构
![](https://i-blog.csdnimg.cn/blog_migrate/397c3b1b8042d97dff5faa5862034ed0.jpeg)
Builder:为创建Product对象的各个部件指定抽象接口。
ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
Director:构造一个使用Builer接口的对象。
Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,以及将这些部件装配成最终产品的接口。
四、举例
- //成品(稳定)
- public abstract class House
- {
- public abstract void addParts(Part part);
- public abstract void houseLable();
- }
- public class RomanHouse : House
- {
- private ArrayList parts = new ArrayList();
- public override void addParts(Part part)
- {
- parts.Add(part);
- }
- private string houseName;
- private string houseType;
- public RomanHouse(string houseName)
- {
- this.houseName = houseName;
- this.houseType = "RomanHouse";
- }
- public override void houseLable()
- {
- Console.WriteLine("The house named " + houseName + " is " + houseType);
- }
- }
- //部件(稳定)
- public abstract class Part
- {
- }
- public class Wall : Part
- {
- public Wall()
- {
- Console.WriteLine("This is write wall.");
- }
- }
- public class Floor : Part
- {
- public Floor()
- {
- Console.WriteLine("This is wooden floor.");
- }
- }
- //构造器(稳定)
- public abstract class Builder
- {
- public abstract void BuildWall();
- public abstract void BuildFloor();
- public abstract House GetHouse();
- }
- public class RomanHouseBuilder : Builder
- {
- private RomanHouse romanHouse;
- public RomanHouseBuilder(string houseName)
- {
- romanHouse = new RomanHouse(houseName);
- }
- public override void BuildWall()
- {
- Part romanWall = new Wall();
- romanHouse.addParts(romanWall);
- }
- public override void BuildFloor()
- {
- Part romanFloor = new Floor();
- romanHouse.addParts(romanFloor);
- }
- public override House GetHouse()
- {
- return romanHouse;
- }
- }
- //具体的组装过程(稳定)
- public class GameManager
- {
- public static House CreateHouse(Builder builder)
- {
- for (int i = 0; i < 4; i++)
- {
- builder.BuildWall();
- }
- builder.BuildFloor();
- return builder.GetHouse();
- }
- }
- //调用
- public class Client
- {
- public static void Main()
- {
- Builder romanHouseBuilder = new RomanHouseBuilder("BUILD 1");
- House romanHouse = GameManager.CreateHouse(romanHouseBuilder);
- romanHouse.houseLable();
- }
- }
现在来分析一下上面的例子:
假设一个游戏中要组装房子,并且房子就是四面墙和地板组成,
房子有Roman风格等等。
Builder对应于组装房子的墙和地板,但需要多少墙和多少地板,不关心。
RomanHouseBuilder继承Builder,对应于组装Roman风格的墙和地板,同时可以返回一间这样风格的房子,但需要多少墙和多少地板,不关心。
House对应于房子。
GameManager表示组装房子的具体过程。
此时我们再来理解下面这句话:“在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定。”
房子就是“一个复杂对象”,它有墙和地板组成,但不管墙和地板这两个部件怎么变化,生产一间房子的过程是不会变的,即组装过程是不会变的。
扩展:
在建立改变或者新建某风格的房子时,需要修改上面的RomanHouseBuilder、Wall、Floor等类。可以将RomanHouseBuilder、Wall、Floor等类放到一起编译成一个类库DLL。进一步,可以将新风格房子编译为DLL,通过配置文件动态添加DLL,然后创建不同风格的房子,这样对于客户来说,需要改变的只是添加一个DLL,改变一个配置文件,原来的其他程序完全不变。程序变成可扩展的程序了。调用程序变为类似这样的:
扩展:
在建立改变或者新建某风格的房子时,需要修改上面的RomanHouseBuilder、Wall、Floor等类。可以将RomanHouseBuilder、Wall、Floor等类放到一起编译成一个类库DLL。进一步,可以将新风格房子编译为DLL,通过配置文件动态添加DLL,然后创建不同风格的房子,这样对于客户来说,需要改变的只是添加一个DLL,改变一个配置文件,原来的其他程序完全不变。程序变成可扩展的程序了。调用程序变为类似这样的:
- //调用
- public class Client
- {
- public static void Main()
- {
- string assemblyName = ConfigurationSettings["BuilderAssembly"];
- string builderClassName = ConfigurationSettings["BuilderClass"];
- string builderName = ConfigurationSettings["BuilderName"];
- Assembly assembly = Assembly.Load(assemblyName);
- Type type = assembly.GetType(builderClassName);
- Builder newTypeHouseBuilder = Activator.CreateInstance(type, new object[] {builderName});
- House newTypeHouse = GameManager.CreateHouse(newTypeHouseBuilder);
- newTypeHouse.houseLable();
- Console.ReadKey();
- }
- }
五、优点
1、建造者模式的“加工工艺”是暴露的,这样使得建造者模式更加灵活。
2、解耦了组装过程和创建具体部件,使得我们不用去关心每个部件是如何组装的。
六、缺点
1、上面的第1点优点也可以说是建造者模式的缺点,因为“加工工艺”的暴露,使此模式不太“优雅”。
七、适用性
1、需要生成的产品对象有复杂的内部结构。
2、需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
3、在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
2、需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
3、在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
八、实现要点
1、建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
2、产品不需要抽象类,特别是由于创建对象的算法复杂而导致使用此模式的情况下或者此模式应用于产品的生成过程,其最终结果可能差异很大,不大可能提炼出一个抽象产品类。
3、创建者中的创建子部件的接口方法不是抽象方法而是空方法,不进行任何操作,具体的创建者只需要覆盖需要的方法就可以,但是这也不是绝对的,特别是类似文本转换这种情况下,缺省的方法将输入原封不动的输出是合理的缺省操作。
3、创建者中的创建子部件的接口方法不是抽象方法而是空方法,不进行任何操作,具体的创建者只需要覆盖需要的方法就可以,但是这也不是绝对的,特别是类似文本转换这种情况下,缺省的方法将输入原封不动的输出是合理的缺省操作。
4、前面我们说过的抽象工厂模式(Abtract Factory)解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化,建造者模式常和组合模式(Composite Pattern)结合使用。
九、.Net Framework中的应用
C#中有一个类叫StringBuilder,输入必要的信息后,就可以取出对应的String。(我们可以将Builder理解成电饭锅,给这个Builder放进去米和水,经过Builder的BuildPart后,我们就可以取出香喷喷的米饭了。)
- using System;
- using System.Text;
- class Exam
- {
- public static void Main()
- {
- StringBuilder sb = new StringBuilder();
- sb.Append('a',2);
- sb.Append('b',3);
- sb.Append('c',4);
- Console.WriteLine(sb.ToString()); //打印出 aabbbcccc
- sb.Remove(0, sb.Length); //清除sb中的所有信息
- }
- }