二十三种设计模式[3]

前言

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

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

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

结构

Builder(1)

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

  • IBuilder(生成器接口):用来定义创建一个产品实例所需要的各个函数;
  • ConcreteBuilder(生成器接口实现):实现创建产品实例所需要的各个函数;

  • Director(导向器):使用生成器来创建产品,封装了产品的实例化逻辑;
  • Product(产品):被导向器创建的产品对象;

实现

       所谓“ 将一个复杂对象的构建与它的表示分离 ”是指由导向器去处理产品的实例化逻辑而不是生成器来处理。再由各个不同的生成器分别实现创建产品实例所需要的各个函数,从而实现“ 同样的构建过程创建不同的表示 ”。

       比如,在一个键鼠套装中包含了一个键盘和一个鼠标,不同的键鼠套装内又有不同品牌、型号的键盘和鼠标。可以通过不同的组合方式得到一个完整的键鼠套装。

Builder(2)

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的各个实现类中。通过这种方式提高了产品的模块性,方便我们后期的维护及扩展。

       如果需要增加新的表示形式(键鼠套装)只需增加一个新的生成器接口实现即可。

Builder(3)

       如果需要对产品进行修改(比如在鼠标套装中增加鼠标垫),除了需要修改产品本身,还需要对生成器和导向器进行对应修改。

Builder(4)

       可以看出,修改产品时的改动量是很大的。为了减少造成的改动,可以将IKitBuilder定义成抽象类并将其所有的函数定义为虚函数,来作为所有子类的缺省函数。这样做的好处是,当我们修改产品时,不必修改每一个生成器(不是每个键鼠套装都包含鼠标垫),只需要修改特定的生成器即可。这种方式虽然能够减少一部分改动,但同时也对我们后期的扩展造成了一部分限制(每个类只能继承一个父类)。所以,采用哪种方式定义生成器还得根据实际需求决定,这里就不一一叙述了。

总结

       生成器,能够使一个复杂对象的创建逻辑与其表示形式分离,将对象本身与对象的创建逻辑解耦,方便程序的维护及扩展。在创建对象的同时能够更加精细的控制对象的创建逻辑,使得这一创建过程更加清晰,并且各个生成器相对独立,通过不同的生成器可获得不同的对象实体,符合开闭原则。

       生成器数量会随着对象表示数量的增加而增加(即对象的表示形式越多,对应的生成器越多),当对象的表示数量过大时,会使程序变得非常臃肿。所以,当对象的表示形式较多或每种表示形式差别较大时,不宜使用该模式。

       以上,就是我对生成器(建造者模式)的理解,希望对你有所帮助。

       示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

       系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

       本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078563.html)

原文地址:https://www.cnblogs.com/wxingchen/p/10078563.html