二十三种设计模式[3] - 生成器模式(Builder Pattern)
前言
生成器,又名建造者模式,属于创建型模式。在《设计模式 - 可复用的面向对象软件》一书中对它的描述为“ 将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示 ”。
与工厂方法和抽象工厂不同的是,工厂方法侧重于将类的实例化延迟到子类,由子类决定工厂的创建,从而得到一个产品,抽象工厂则是一个包含了多个工厂方法的大型工厂,更侧重于一系列产品的创建,而生成器在创建产品同时,更加关注于产品的创建逻辑。由于它们的侧重点不同,工厂方法适合创建复杂的产品,生成器适合创建更加复杂的产品。
一个简单的例子。抽象工厂代表了一个生产多种产品的代工厂,工厂方法代表了某个产品的生产车间,生成器则代表了某个产品的生产线。
结构
在生成器中,需要如下几种角色的支持:
- IBuilder(生成器接口):用来定义创建一个产品实例所需要的各个函数;
-
ConcreteBuilder(生成器接口实现):实现创建产品实例所需要的各个函数;
- Director(导向器):使用生成器来创建产品,封装了产品的实例化逻辑;
- Product(产品):被导向器创建的产品对象;
实现
所谓“ 将一个复杂对象的构建与它的表示分离 ”是指由导向器去处理产品的实例化逻辑而不是生成器来处理。再由各个不同的生成器分别实现创建产品实例所需要的各个函数,从而实现“ 同样的构建过程创建不同的表示 ”。
比如,在一个键鼠套装中包含了一个键盘和一个鼠标,不同的键鼠套装内又有不同品牌、型号的键盘和鼠标。可以通过不同的组合方式得到一个完整的键鼠套装。
public interface IMouse { string GetBrand(); } public class LogitechMouse : IMouse { public string GetBrand() { return "罗技-Logitech G903"; } } public class RazeMouse : IMouse { public string GetBrand() { return "雷蛇-Raze 蝰蛇"; } } public interface IKeyBoard { string GetBrand(); } public class LogitechKeyboard : IKeyBoard { public string GetBrand() { return "罗技-Logitech G910 RGB"; } } public class RazeKeyboard : IKeyBoard { public string GetBrand() { return "雷蛇-Raze 萨诺狼蛛"; } } public class Kit { public IMouse Mouse { set; get; } = null; public IKeyBoard Keyboard { set; get; } = null; } public interface IKitBuilder { void BuildMouse(); void BuildKeyboard(); Kit GetKit(); } public class LogitechKitBuilder : IKitBuilder { private Kit _kit = null; public LogitechKitBuilder() { this._kit = new Kit(); } public void BuildMouse() { this._kit.Mouse = new LogitechMouse(); } public void BuildKeyboard() { this._kit.Keyboard = new LogitechKeyboard(); } public Kit GetKit() { return this._kit; } } public class RazeKitBuilder : IKitBuilder { private Kit _kit = null; public RazeKitBuilder() { this._kit = new Kit(); } public void BuildMouse() { this._kit.Mouse = new RazeMouse(); } public void BuildKeyboard() { this._kit.Keyboard = new RazeKeyboard(); } public Kit GetKit() { return this._kit; } } public class KitDirector { public Kit ConstructKit(IKitBuilder builder) { if(builder == null) { return null; } builder.BuildMouse(); builder.BuildKeyboard(); return builder.GetKit(); } } class Program { static void Main(string[] args) { //创建生成器 IKitBuilder builder = new LogitechKitBuilder(); //IKitBuilder builder = new RazeKitBuilder(); //创建导向器 KitDirector director = new KitDirector(); //导向器通过注入的生成器创建产品实体 Kit kit = director.ConstructKit(builder); Console.WriteLine($"当前套装内的鼠标是:{kit.Mouse.GetBrand()}"); Console.WriteLine($"当前套装内的键盘是:{kit.Keyboard.GetBrand()}"); Console.ReadKey(); } }
上述代码中,我们通过将不同的生成器(IKitBuilder)实体注入到同一导向器(KitDirector)来获得不同的Kit实体,从而达到“ 同样的构建过程创建不同的表示 ”的目的。在入口Program类中并不知道Kit是如何被创建的,它的创建逻辑被封装在导向器KitDirector中,而它的表示和内部结构则被封装在生成器IKitBuilder的各个实现类中。通过这种方式提高了产品的模块性,方便我们后期的维护及扩展。
如果需要增加新的表示形式(键鼠套装)只需增加一个新的生成器接口实现即可。
如果需要对产品进行修改(比如在鼠标套装中增加鼠标垫),除了需要修改产品本身,还需要对生成器和导向器进行对应修改。
可以看出,修改产品时的改动量是很大的。为了减少造成的改动,可以将IKitBuilder定义成抽象类并将其所有的函数定义为虚函数,来作为所有子类的缺省函数。这样做的好处是,当我们修改产品时,不必修改每一个生成器(不是每个键鼠套装都包含鼠标垫),只需要修改特定的生成器即可。这种方式虽然能够减少一部分改动,但同时也对我们后期的扩展造成了一部分限制(每个类只能继承一个父类)。所以,采用哪种方式定义生成器还得根据实际需求决定,这里就不一一叙述了。
总结
生成器,能够使一个复杂对象的创建逻辑与其表示形式分离,将对象本身与对象的创建逻辑解耦,方便程序的维护及扩展。在创建对象的同时能够更加精细的控制对象的创建逻辑,使得这一创建过程更加清晰,并且各个生成器相对独立,通过不同的生成器可获得不同的对象实体,符合开闭原则。
生成器数量会随着对象表示数量的增加而增加(即对象的表示形式越多,对应的生成器越多),当对象的表示数量过大时,会使程序变得非常臃肿。所以,当对象的表示形式较多或每种表示形式差别较大时,不宜使用该模式。
以上,就是我对生成器(建造者模式)的理解,希望对你有所帮助。
示例源码:https://gitee.com/wxingChen/DesignPatternsPractice
系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078563.html)