博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
二十三种设计模式[3] - 生成器模式(Builder Pattern)
阅读量:4635 次
发布时间:2019-06-09

本文共 3792 字,大约阅读时间需要 12 分钟。

前言

       生成器,又名建造者模式,属于创建型模式。在《设计模式 - 可复用的面向对象软件》一书中对它的描述为“ 将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示 ”。

       与和不同的是,工厂方法侧重于将类的实例化延迟到子类,由子类决定工厂的创建,从而得到一个产品,抽象工厂则是一个包含了多个工厂方法的大型工厂,更侧重于一系列产品的创建,而生成器在创建产品同时,更加关注于产品的创建逻辑。由于它们的侧重点不同,工厂方法适合创建复杂的产品,生成器适合创建更加复杂的产品。

       一个简单的例子。抽象工厂代表了一个生产多种产品的代工厂,工厂方法代表了某个产品的生产车间,生成器则代表了某个产品的生产线。

结构

在生成器中,需要如下几种角色的支持:

  • 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://www.cnblogs.com/wxingchen/p/10078563.html)

转载于:https://www.cnblogs.com/wxingchen/p/10078563.html

你可能感兴趣的文章
获得屏幕像素以及像素密度
查看>>
2018考研英语:10篇必背的真题文章
查看>>
int与string转换
查看>>
用easyui动态创建一个对话框
查看>>
adb命令 判断锁屏
查看>>
centos7下安装docker
查看>>
推荐一个MacOS苹果电脑系统解压缩软件
查看>>
命令行编译运行CSharp文件
查看>>
HDOJ 1060 Leftmost Digit
查看>>
1035等差数列末项计算
查看>>
ASP.NET MVC 2示例Tailspin Travel
查看>>
nonatomic, retain,weak,strong用法详解
查看>>
第10周进度条
查看>>
编写函数求两个整数 a 和 b 之间的较大值。要求不能使用if, while, switch, for, ?: 以 及任何的比较语句。...
查看>>
CDMA鉴权
查看>>
ASP.NET MVC Identity 兩個多個連接字符串問題解決一例
查看>>
#include<bits/stdc++.h>包含C++的所有头文件
查看>>
Vue插槽 slot
查看>>
软考之路-网络攻击:主动攻击和被动攻击
查看>>
《windows核心编程系列》二谈谈ANSI和Unicode字符集
查看>>