zoukankan      html  css  js  c++  java
  • 我也设计模式——4.Builder

    但凡是模式,总要有一个UML才能说清楚。一般而言,我只记UML图,有些模式有自己的固定实现代码(只是其中一部分),尤其在C#中,甚至只是把用户当作一个Client端,而不暴露其内部实现,只把封装好的模式接口公布。于是,我会再记一些固定方法实现。还有就是适用场合(多看例子),优缺点。最后,横向的看模式与模式之间的比较,联合与差异。以上都做到了,这套心法也就成正果了。

    OO的思想也很重要,三个基本特征尤其是多肽,接口与虚拟类,构造体与类,委托,索引,泛型,都是很值得探讨的。

    开始正题,先给出Builder的UML图,如下:


    Builder:生成器,抽象接口,有BuildPart()这样的若干虚方法。
    ConcreteBuilder:具体实现者,GetResult()方法返回生成的产品。
    Director:导航器,保持一个对接口的引用,可以被Client端实例化,从而通过Concrete()方法(见下),间接操纵ConcreteBuilder中的BuildPart()方法。

            public void Concrete()
            
    {
                b.BuildPart();  
    //这里b来自Director构造函数中的Builder型参数
            }

    这样子,在Director类中,就实现了面向接口编程,即导航器Director在不知道具体是什么样的ConcreteBuilder时,建立接口“对象”b,访问其BuildPart()虚方法。
    于是在Client端,我可以这么写:

            ConcreteBuilder b = new ConcreteBuilder();
            Director c 
    = new Director (b);  //将具体生成类放入导航器
            c.Concrete();
            
    //这时调用b.GetResult();   返回生成的对象,即产品。

    可以看到,如果要使用ConcreteBuilderB,而不使用原来的ConcreteBuilder类,那么只要更改Client端第1句话为
    ConcreteBuilderB b = new ConcreteBuilderB(); 其他都不用更改。这就是Builder模式所要追求的效果。
    可以看“画脸谱”这样一个例子,体会Builder模式的使用。代码如下:

        public abstract class FaceBuilder
        
    {
            
    public abstract void BuildFace();
        }


        
    public class GUIFaceBuilder : FaceBuilder
        
    {

            
    public override void BuildFace()
            
    {
                grap.DrawEllipse(p, 
    100100200300);
            }


            
    private Graphics grap;
            
    private Pen p;

            
    public GUIFaceBuilder()
            
    {
                grap 
    = g;
                p 
    = new Pen(Color.Red);
            }

        }



        
    public class FaceDirector
        
    {
            
    private FaceBuilder b;

            
    public FaceDirector(FaceBuilder b)
            
    {
                
    this.b = b;
            }


            
    public void CreateFace()
            
    {
                b.BuildFace();
            }

        }

    注意,这个Sample的生成器中没有GetResult方法,这是因为已经得到g并进行画图,所以不再需要返回产品了,从而省略之。

    让我们回过头来,看Builder模式的意图:
    将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。——《设计模式》
    注意1:对象构建过程被封装在Director中,具体的表示由生成器(ConcreteBuilder)完成。从而构建与表示彻底分离。
    注意2:相同的Director(构建过程)。
    注意3:不同的表示(ConcreteBuilder),上文的ConcreteBuilder对象与ConcreteBuilderB对象具有不同的类型,也就是说,Builder模式不关心产生的结果。
     
    和抽象工厂的区别:前不久面试,被问到这里卡住了,其实很简单啦,抽象工厂生成一个产品系列,接口相同;而Builder模式产生的成品可以有不同接口,只要创建过程是同样的方法就可以。还有就是,如果是抽象工厂,Client端不需要知道返回对象的具体类型,因为抽象工厂构造的是具有相同接口的产品系列;如果是Builder模式,用户必须知道返回对象的具体类型,因为不同的生成器返回的类型是不同的。

    另一个例子是登录界面,既支持Windows应用,又支持Web应用,这是两个不同接口的产品界面,但画出来的UI是一样的,所以要用Builder模式实现。只要保证在虚方法实现时把不同的控件扔进Panel,最后通过GetResult返回Panel对象。在各自不同的Clinet端使用这个Panel。

    注意,Builder模式=Builder+Director,二者缺一不可;.NET平台有一个StringBuilder,这只是Builder,与模式没关系。

    另外,.NET平台中,Page类中的OnInit()等方法的实现。我们在写一个Web页面的时候。他的codebehind代码都是继承System.Web.UI.Page基类的。OnInit()函数是可以重写的,这也是Builder模式的应用之一:导航器和Builder接口封装在Framework中。生成器也在封装在Framework中,只是对外暴露一个虚方法实现OnInit(),由你来实现。Client端调用机制也在Page生成周期中自动实现。——.NET充分利用了模式来方便开发者,但是不说清楚,一般开发人是不会留意的。

    补注:
    1. GetResult()方法的位置。
       文中讲的是放在具体生成器中。可不可以放在Builder接口中呢,或者省略这个方法呢?答案是可以的。
       1)当不需要返回一个产品时,就不需要GetResult这个方法。比如说文中讲的画脸谱,在BuildPart()方法中就已经实现了g.Draw这样的操作,这时候是否生成产品就不重要了。
       2)当需要返回一个产品时,分两种情况:如果这些产品具有相同的接口,则可以将GetResult()方法抽象到Builder接口,因为返回相同的产品类型;但是如果这些产品的接口不同,则必须各自实现在具体的生成器中。

    2. Builder的缺点:不方便添加一个新BuilderPart()方法。这是因为,如果需要增加新的BuilderPart(),要同时改动接口,原有的生成器,还有导航器,也就是说,所有的都要改变,这是不可以的。

    3. AbstractFactory认为构造过程和生成都是很简单的,这时候,如果这些产品具有相同的接口,使用抽象工厂可好,而不使用Builder。

    4.既然Director和Builder接口都是不便的,那么是否可以不要Director,而把其Concrete()方法放入Builder接口中呢?同时不暴露Builder接口的BuildPart()方法,将其访问权限设为protected(允许具体生成器重写实现)。新的UML图如下:



    代码相应为:
        public abstract class Builder
        
    {
            
    protected abstract void BuildPartA();
            
    protected abstract void BuildPartB();

            
    public void Concrete()
            
    {
                
    this.BuildPartA();
                
    this.BuildPartB();
            }

        }


        
    public class ConcreteBuilder : Builder
        
    {

            
    protected override void BuildPartA()
            
    {
                Console.WriteLine();
            }


            
    protected override void BuildPartB()
            
    {
                Console.WriteLine();
            }


            
    public string GetResult()
            
    {
                
    return "1";
            }

        }


        
    public class Client
        
    {
            
    public void TestBuilder()
            
    {
               ConcreteBuilder bui 
    = new ConcreteBuilder();
               bui.Concrete();
            }

        }

    这样一来,就蜕化成为Template模式了,已经体现不出来Builder的宗旨。尤其是当对应于一套Builder,存在多个导航器的时候,确实需要抽象出一个Director来。
  • 相关阅读:
    加载db2的驱动类,连接数据库URL下的特定schema并插入数据
    vi修改编辑文件提示Write Error!(File System full?)研究与解决
    java实现一次性压缩多个文件
    java转义字符
    统计数字问题0
    系统开关机
    统计数字问题1
    算法的力量
    半数集0
    wBox是一款轻量级的弹出窗口插件
  • 原文地址:https://www.cnblogs.com/Jax/p/707391.html
Copyright © 2011-2022 走看看