zoukankan      html  css  js  c++  java
  • 二十三种设计模式[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)

  • 相关阅读:
    CSS------制作一个带+-的input框
    rest简介
    flask之flask-login登陆验证(一)
    Python之异常设计(一)
    flask之flask-sqlalchemy(一)
    flask之wtforms 表单验证(一)
    三 Django模型层之Meta
    二 Djano模型层之模型字段选项
    一 Django模型层简介
    Django之路由、模板和模型系统 (转载)
  • 原文地址:https://www.cnblogs.com/wxingchen/p/10078563.html
Copyright © 2011-2022 走看看