c#中建造者设计模式详解
一、基础介绍:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
说白了就是将一个复杂的对象拆分成一个一个零件,然后按照既定顺序和规则进行组装,最终形成这个相对复杂的对象。
具体可分为4个角色:
Product(产品):复杂对象本身。
Builder(抽象建造者):既可以是抽象类也可以是接口,主要是为了约束和规范具体建造者有哪些零件,并提供一个方法返回组装后的复杂对象。
ConcreteBuilder(具体建造者):它继承自Builder(抽象建造者),主要是具体实现父类中的那些零件。也就是说在这个类里就要实际去创建各个零件的具体功能了。
Director(指挥者):又称为导演类,在指挥者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象,然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。
- 特性和功能:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 使用环境:当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 注意事项:建造者模式的使用需要考虑其复杂性,如果产品结构较简单,使用此模式可能会增加系统的复杂性。
- 优点:客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦。
- 缺点:产品的组成部分必须相同,限制了其使用范围。
二、应用场景:
有时需要创建一个复杂对象,其通常由其各部分子对象通过一定的步骤组合而成。
由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
本质就是:创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
使得相同的创建过程可以创建不同的产品。
造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。
- 垃圾食品套餐系统:汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出各种特惠"套餐"。
- 装修系统:改水电、再贴瓷砖、家电、电视墙等,顺序基本不变,用材不同最终生成的产品则不同。
- ......
三、创建方式:
本实例主要是中Computer是产品,包含CPU、主板、内存、硬盘、显卡、机箱等组件。
Builder是抽象建造者,HighPerformanceBuilder和CostEffectiveBuilder是具体的建造者,Director是指挥者。
- 首先要确定产品类
1 /// <summary> 2 /// 产品类 3 /// </summary> 4 public class Computer 5 { 6 public string CPU { get; set; } 7 public string Memory { get; set; } 8 public string Disk { get; set; } 9 }
- 其次是抽象建造者类
1 /// <summary> 2 /// 抽象建造者类 3 /// </summary> 4 public abstract class BuilderComputer 5 { 6 //产品对象---protected关键字受保护成员可以在其所在的类、其子类以及同一个命名空间中的其他类中被访问。 7 protected Computer computer = new Computer(); 8 9 //以下方法约束了产品由哪些零件组成 10 public abstract void BuildCPU(); 11 public abstract void BuildMemory(); 12 public abstract void BuildDisk(); 13 14 /// <summary> 15 /// 返回对象本身 16 /// </summary> 17 /// <returns></returns> 18 public Computer GetComputer() 19 { 20 return computer; 21 } 22 }
该类中限定了产品的组成部分也就是各个零件。
这里指明了产品由三个零件组成,分别是CPU、Disk和Memory。
- 再者是具体建造者类
1 /// <summary> 2 /// 低配电脑 具体的创建者 3 /// </summary> 4 class LowComputer : BuilderComputer 5 { 6 public override void BuildCPU() 7 { 8 computer.CPU = "i5处理器"; 9 } 10 11 public override void BuildDisk() 12 { 13 computer.Disk = "512G固态"; 14 } 15 public override void BuildMemory() 16 { 17 computer.Memory = "16G内存"; 18 } 19 } 20 21 /// <summary> 22 /// 高配电脑 具体的创建者 23 /// </summary> 24 class GoodComputer : BuilderComputer 25 { 26 public override void BuildCPU() 27 { 28 computer.CPU = "i7处理器"; 29 } 30 31 public override void BuildDisk() 32 { 33 computer.Disk = "1T固态"; 34 } 35 public override void BuildMemory() 36 { 37 computer.Memory = "32G内存"; 38 } 39 }
上述代码中定义了两个具体建造者,分别是低配电脑和高配电脑。
继承自抽象建造者类,并具体实现了其中的零件。
如果还有还想新增其他配置点电脑,就可以新增一个具体建造者类,而无需修改其他代码。
- 最后是指挥者
1 /// <summary> 2 /// 指挥者-监工 创建对象的顺序 3 /// </summary> 4 public class Director 5 { 6 private BuilderComputer _builder = null; 7 8 /// <summary> 9 /// 通过构造函数传递具体创造者 10 /// </summary> 11 /// <param name="builder"></param> 12 public Director(BuilderComputer builder) 13 { 14 this._builder = builder; 15 } 16 17 /// <summary> 18 /// 组装方法,并返回产品 19 /// </summary> 20 /// <returns></returns> 21 public Computer AssembleComputer() 22 { 23 _builder.BuildCPU(); 24 _builder.BuildDisk(); 25 _builder.BuildMemory(); 26 return _builder.GetComputer(); 27 } 28 }
在上述代码中是通过构造函数传递具体创造者,也可以在AssembleComputer方法中传递。
产品的具体组装规则则是由AssembleComputer方法来完成,如果有多种组装方式,也可以有多个方法来分别完成。
该类本质就是统筹安排,并直接与客户端进行交互。
- 客户端调用
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("我想组装一台低配电脑:"); 6 //首先实例化一个低配电脑的具体建造对象 7 BuilderComputer builderComputer = new LowComputer(); 8 //然后使用指挥者类来具体组装这个产品 9 Computer director = new Director(builderComputer).AssembleComputer(); 10 Console.WriteLine("组装完毕,具体配置如下:"); 11 Console.WriteLine(director.CPU); 12 Console.WriteLine(director.Memory); 13 Console.WriteLine(director.Disk); 14 15 16 Console.WriteLine("\n我又想组装一台高配电脑:"); 17 //首先实例化一个高配电脑的具体建造对象 18 builderComputer = new GoodComputer(); 19 //然后使用指挥者类来具体组装这个产品 20 director = new Director(builderComputer).AssembleComputer(); 21 Console.WriteLine("组装完毕,具体配置如下:"); 22 Console.WriteLine(director.CPU); 23 Console.WriteLine(director.Memory); 24 Console.WriteLine(director.Disk); 25 26 } 27 }
- 指挥者类也可以省略,组装交给抽奖建造者来完成
1 /// <summary> 2 /// 抽象建造者类 3 /// </summary> 4 public abstract class BuilderComputer 5 { 6 //产品对象---protected关键字受保护成员可以在其所在的类、其子类以及同一个命名空间中的其他类中被访问。 7 protected Computer computer = new Computer(); 8 9 //以下方法约束了产品由哪些零件组成 10 public abstract void BuildCPU(); 11 public abstract void BuildMemory(); 12 public abstract void BuildDisk(); 13 14 /// <summary> 15 /// 可以省略掉指挥者类,由此方法进行组装 16 /// </summary> 17 /// <returns></returns> 18 public Computer Construct() 19 { 20 this.BuildCPU(); 21 this.BuildMemory(); 22 this.BuildDisk(); 23 return computer; 24 } 25 }
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("我想组装一台低配电脑:"); 6 Computer director = new LowComputer().Construct(); 7 Console.WriteLine("组装完毕,具体配置如下:"); 8 Console.WriteLine(director.CPU); 9 Console.WriteLine(director.Memory); 10 Console.WriteLine(director.Disk); 11 12 13 Console.WriteLine("\n我又想组装一台高配电脑:"); 14 director = new GoodComputer().Construct(); 15 Console.WriteLine("组装完毕,具体配置如下:"); 16 Console.WriteLine(director.CPU); 17 Console.WriteLine(director.Memory); 18 Console.WriteLine(director.Disk); 19 20 } 21 }
在上述代码中可以看出,此处还可以定义了一个虚方法,
在具体建造者中,可以根据需要重写该方法,使其返回实际需要的值,然后在构建过程中,使用该值进行构建。相较于指挥者类,具体产品的组装可以交由产品本身去组装。
四、总结:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,并不适合使用建造者模式。
建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。
需要生产的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量,产品对象的属性相互依赖,需要指定其生成顺序。
对象的创建过程独立于创建该对象的类,隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
附:创建者模式对比
工厂模式解决了“同一类产品”的需求变化,抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。
工厂方法模式 VS 建造者模式
工厂方法模式侧重整体对象的创建方式,建造者模式侧重零部件的构建,然后通过一定顺序和规则构造出一个复杂的对象。
例如:想要制作一个假人,如果使用工厂方法模式,直接生产出来一个XX材质、XX高、XX重的假人人就可以了。
而如果使用建造者模式,则需要先创建出四肢、头和躯干等部位,然后按照一定顺序进行组装形成一个完整的假人。
抽象工厂模式 VS 建造者模式
抽象工厂模式侧重对产品族(系列产品)的创建,一个产品族是这样的一系列产品。
采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则侧重要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。