UML软件工程组织

设计模式简述
IT先锋资深顾问 grid liu

摘要

关键词:设计模式  创建型模式  简单工厂  工厂方法  抽象工厂  

1创建型模式

1.1创建对象

在面向对象语言中的创建对象,使用class的构造函数,通过操作生成一个对象(该类的实例instance)。如java语言的new,如我们创建一个Customet类的实例,Customer object=new Customer();并且每个对象都有一个构造函数(可以是缺省的构造函数),它负责对象的创建,(有些语言还有析构函数,负责对象的释放,然而Java使用垃圾收集器完成对象的回收)

1.2为什么需要创建型模式

我们已经知道了在java语言中通过new来创建对象,但是什么还需要创建模式,其中有以下几个原因:

·由于设计模式的原则,面向接口进行编程,而不是实现(Programming to an Interface, not an Implementation)

因为在实际的编程时使用了接口(Interface)的概念,可是该接口一般有多个实现(Implementation)类,因此就会存在如何选择的创建哪个实现的实例情况,如下图:

因此我们传统的创建对象,不利于扩展,修改。如Interface object=new Implementtion1();这样我们一旦修改为其他的实现类,需要多处修改代码,因此需要把创建对象外部化。

·名称必须是构造函数,名称受限

往往构造函数自身无法描述被返回的对象,而选用名字合适的工厂方法可以使类的使用更加容易,产生的代码容易阅读。

·每次创建都创造了一个新的对象

每次创建都返回一个新的对象,然而在实际项目中需要,某一类仅创建一个实例,或者为了使用Cache,来进行反复使用已经创建的对象。

·每次的返回类型,都是一个具体对象

构造函数返回的类型,都是具体的类型。而使用工厂模式可以返回子类型。

·重用创建对象的过程

我们知道,有些对象的创建过程相当复杂,这里指的是通过new之后的Object,必须经过一系列的赋值等操作之后才可使用。于是我们需要这些赋值等操作的过程。

1.3创建型模式目的

创建型模式目的就是抽象对象的实例化过程。它可以帮助一个系统独立于如何创建,谁创建,创建什么。它可以改变以前通过new Class()这种硬编码方式转移为一个独立的行为集。因此这些行为可以实现复用,组合,使用创建对象的过程易于修改,维护,扩展。

1.4创建型模式的考虑点

在实际的系统中使用创建型模式创建对象时需要考虑以下几点:

·动态的确定创建哪些对象(确定创建一个接口的那个实现类)

·是谁创建这些对象(是外部的对象,还是本身对象提供,或者静态的类继承)

·动态决定怎样创建对象(新建对象还使用Cache,还是复制某个对象)

·决定怎样组合或者表示这些对象()

1.5创建模式的分类

·类的创建模式

类的创建模式使用继承关系,把类的创建延迟到子类,从而封装了客户端将得到哪些具体类的信息

·对象的创建模式

而对象的创建则是把对象的创建过程动态的委派给另一个对象,从而动态的决定客户端将得到哪些具体的类实例,以及这些类的实例是如何被创建和组合在一起

2简单工厂模式

2.1意图

·简单工厂是由一个工厂对象决定创建出哪一种产品类的实例。

 简单工厂是最简单的一种模式(我认为还有Template Method模式),也最容易理解的模式,上面介绍使用传统方式创建对象的缺点,因此通过外部化行为创建对象,因此我们通过一个方法,它负责对象的创建。因此把这种传统的创建过程Customer Object=New Customer()改为  Customer Object= createObject();

Public Customer createObject(){

       Return new New Customer();

}

这样我们在系统中创建对象改为使用一个函数来完成,因此一旦系统发生改变,我们仅需要修改这个函数即可完成。

 在实际的项目中,不可能所有的创建对象都使用该种方式,我们应该考虑只有在一个接口有多个实现时,我们可以把这种选择实现类时的业务逻辑进行封装到这个函数中。

2.2结构以及参与者

简单工厂模式就是由一个工厂类可以根据传入的参数决定创建出哪一种具体产品类的实例。

 

·工厂类(Creator)角色:该角色是工厂方法模式的核心,含有按照一定商业逻辑创建产品。工厂类在客户端的直接调用下创建产品对象,它往往由一个具体Java 类实现。

·抽象产品(Product)角色:担任这个角色的类是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。抽象产品角色可以用一个Java 接口或者Java 抽象类实现。

·具体产品(Concrete Product)角色:工厂方法模式所创建的任何对象都是这个角色的实例,具体产品角色由一个具体Java 类实现。

2.3Sample

2.3.1XML解析器

2.3.1.1 概述

JAXP是使用Java语言编写的编程接口(Java API for XML Processing)用于XML文档处理。JAXP支持DOM、SAX、XSLT等标准。为了增强JAXP使用上的灵活性,SUN的规范开发者特别为JAXP设计了一个Pluggability Layer,在Pluggability Layer的支持之下,JAXP既可以和具体实现DOM API、SAX API 的各种XML解析器(例如Apache Xerces)联合工作,应用Pluggability Layer的好处在于:我们只需要熟悉JAXP各个编程接口的定义即可,而不需要对所采用的具体的XML解析器有很深入的了解。比如在某个Java程序中,通过JAXP调用XML解析器Apache Crimson对XML文档进行处理,如果我们希望使用别的XML解析器(比如Apache Xerces),以便提高该程序的性能,那么原程序代码可能不需要任何改变,直接就可以使用配置即可。这其实就与我们的JDBC驱动一样,SUN提供一些接口,然后有各个厂家进行实现。

因此,SUN定义一个使用DOM方式解析的接口DocumentBuilderFactory,它其实使用后来要介绍的工厂方法模式,它主要负责创建一些DOM类,这里我们先介绍它本身的创建。因此不同的DOM解析厂家有不同的实现,如Apache Xerces或者Oracle等。因此我们的开发人员就要面临一个问题怎样创建这些实现对象,这里就使用了简单工厂方法。

 

在进行解析之前首先必须获得DocumentBuilderFactory的对象,然后使用它生成其他对象如DocumentBuilder等,其过程如下:

try {

  DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();

  DocumentBuilder builder = factory.newDocumentBuilder();

  document = builder.parse( new File(argv[0]) );

} catch (SAXParseException spe) {

}

这这里我们知道DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();是通过简单工厂完成创建的,实际上它是通过FactoryFinder这个简单工厂完成创建的

2.3.1.2 实现源码

我们可以通过FactoryFinder实现源码如下:它首先通过配置的信息获得解析器的类名称,然后进行实例化这个类。如下面的配置信息:

javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl

       try {

            Class     spiClass = Class.forName(className);

            return spiClass.newInstance();

        } catch (ClassNotFoundException x) {

        }

}

这样我们如果需要更换XML解析器,只需修改这个配置信息,即可完成,而不需要我们修改源码。这其实也体现面向接口编程的优点。

2.4实现方法

2.4.1 工厂方法的实现

在一个工厂类中一般包含条件的选择生成其中哪一个类的实例:

 public class Creator {

    public static Product factory(int i) {

         if (i == 1) {

               return new ConcreteProduct1();

         } else if (i == 2) {

               return  new ConcreteProduct2();

         }

   }

}

这种代码中充满着大量的if else很难进行维护,不符合Open-Closed原则,因此在实际应用简单工厂模式时,不会有人这样来实现。一般把参数和具体实现类进行配置,就如我们上面XML解析器的实现一样,它是通过参数javax.xml.parsers.DocumentBuilderFactory进行配置,如使用apache则设置org.apache.xerces.jaxp.DocumentBuilderFactoryImpl,你如果修改其他的解析器只需修改该配置即可。我们知道在Java语言中可以通过两种方式根据一个Class的名称,来创建改类的实例;

 String className=FromConfigGetClassName();//这里根据配置信息获得  

Class     classinstance = Class.forName(className).newInstance();

或者通过下面方式

String className=FromConfigGetClassName();//这里根据配置信息获得

Class  classinstance = getClass().getClassLoader().loadClass(className).newInstance();

这样一般根据参数进行配置到java的properties或者XML文件中

如在SUN Petstore中的Action,就是根据客户端的请求URLPath,来生成相应的Action,其实在Struts也是这样的,

配置信息

<url-mapping url="order.do" isAction="true">

        <action-class> OrderHTMLAction</action-class>

</url-mapping>

<url-mapping url="signoff.do" isAction="true">

        <action-class> SignOffHTMLAction</action-class>

</url-mapping>

<url-mapping url="createuser.do" isAction="true">

        <action-class> CreateUserHTMLAction</action-class>

</url-mapping>

 它的简单工厂方法如下:

HTMLAction getAction(String selectedUrl) {

        HTMLAction handler = null;

        URLMapping urlMapping = getURLMapping(selectedUrl);

        String actionClassString = null;

        if (urlMapping != null) {

            actionClassString = urlMapping.getAction();//获得配置Action类名

            if (urlMapping.isAction()) {

                try {

                    handler = (HTMLAction)getClass().getClassLoader().loadClass(actionClassString).newInstance();

                } catch (Exception ex) {

                    System.err.println("RequestProcessor caught loading action: " + ex);

                }

            }

        }

        return handler;

    }

2.4.2  简单工厂的角色合并

在实际的应用中,上述结构的三种角色会存在各种合并,如工厂角色和产品角色,抽象产品和具体产品等。这在《java与模式》中有描述。

2.4.3 关于产品的缓存

在我们上述的例子中,都是每次进行创建一个新的对象,然而在实际的应用中经常使用缓存,如在Struts的Action的创建时,它就使Action仅有一个实例,当然这种创建不可能使用singleton模式,因为这会每个类都实现那些方法,于是他们使用一个cache,首先进行判断,如果已经存在实例,就使用,如果没有就创建。以下Struts的源码

    protected Action processActionCreate(HttpServletRequest request,

                                         HttpServletResponse response,

                                         ActionMapping mapping)

        throws IOException {

 

        // Acquire the Action instance we will be using (if there is one)

        String className = mapping.getType();

        Action instance = null;

        synchronized (actions) {

 

            // Return any existing Action instance of this class

            instance = (Action) actions.get(className);

            if (instance != null) {

                return (instance);

            }

 

            // Create and return a new Action instance

            try {

                instance = (Action)

                    RequestUtils.applicationInstance(className);

                instance.setServlet(this.servlet);

                actions.put(className, instance);

            } catch (Throwable t) {

                return (null);

            }

        }

        return (instance);

    }

注:如果在一个系统中我可以使用一个简单工厂来每次都创建新的对,也可以使用另外一个工厂进行缓存,这就使用两个以上的简单工厂方法,于是就需要进行把工厂进行抽象,因而就引入了下面的模式-工厂方法模式。

3  工厂方法模式

3.1 意图

定义创建对象的接口,让子类决定实例化哪个类。FactoryMethod将实例化的工作委派给子类。

这句话是摘自《设计模式》,但是我认为实际在使用它主要是因为对工厂进行抽象,使用多个工厂(实现不同的策略),进行创建产品。如果你理解简单工厂的话,你可以这样认为他是简单工厂的一种抽象。抽象工厂,可以有不同的工厂来实现。

 注:我理解当初工厂方法的定义是类模式,他使用继承来完成创建的,如果你熟悉templeate Method模式对这种方式很容易理解,我们定义一个抽象方法,这个方法来完成产品的创建,还有模板方法,它是一个骨架调用抽象的创建方法,如何子类进行实现这个抽象方法,因此就是实现,使用不同的产品。结构图如下:

简单代码:

 public abstract  class appliaction  {

   //该方法是工厂方法,由子类实现

   public abstract document createDocument();

   //模板方法,在方法中使用了工厂方法

   pubic  void newDocument(){

       //使用工厂方法,获得doc,而实际有子类完成,

       //这里可以看出,对象的创建,是通过继承来实现,是静态的,编译时已经确定

       //而不是通过对象的委托来实现,故属于类创建型模式

       Document doc=createDocument();

       doc.open();

       ....................

       doc.save();

   }

 }

 具体的工厂实现由子类来实现工厂方法:

 public  class myappliaction extends  appliaction {

    public document createDocument(){

           return new Mydocument();

    }

 }

但是我感觉,这种使用的方式逐渐再减少.

3.2        结构与参与者

Creator依赖其子类定义的工厂方法,因而返回相应的ConcreteProduct。

·Product

°定义工厂方法所创建对象的接口

·ConcreteProduct

°实现Product声明的接口

·Creator

°声明返回类型为Product的对象。同时,Creator有可能定义返回默认ConcreteProduct的工厂方法的默认实现。

°可能调用工厂方法创建产品对象

·ConcreteCreator

覆盖工厂方法,返回ConcreteProduct的一个实例。

3.3什么情况下使用

我认为学习模式时,一定要理解一个模式的意图,结构,使用性。只有这样你才可能在实际的项目中使用,具体的实现时的细节可以翻阅资料,只有你非常了解模式时,才会很自然的应用模式。

·类无法预知所创建对象的类型。

这样情况在J2EE的规范里,有很多的实例,大家知道因为SUN的规范实际上就是一些接口,然后采用各个厂家进行实现,因此它在实例化时,根本就不知道实例化那些具体的实现类,因此它们的工厂也是抽象的,由具体的厂家来实现,我们举的示例就是这种情况。

·类让它的子类确定所创建的对象。

这就是因为我们存在多个具体工厂来创建相同的产品,只因为它们的策略不同而已,于是需要把工厂进行抽象,这种情况一般会在我们实际项目中出现。

·类将职责分派给若干个辅助子类之一,同时将所分派子类的具体信息局部化。

这种情况就是我们上面的示例,是Template Method模式的特例。

3.4Sample Code

我们在简单工厂模式里介绍了DocumentBuilderFactory是通过简单来创建的,在使用DOM进行解析的过程里,是使用DocumntBuilder进行解析的它返回Document获得,它们的信息。但是DocuemntBuilderFactory它并不知道的DocumntBuilder的具体实现类,因此在DocuemntBuilderFactory

public abstract class DocumentBuilderFactory {  

       public abstract DocumentBuilder newDocumentBuilder()

            throws ParserConfigurationException;

 }   

public class DocumentBuilderFactoryImpl extends DocumentBuilderFactory {

      public DocumentBuilder newDocumentBuilder()throws ParserConfigurationException

    {

            try {

                   return new DocumentBuilderImpl(this, attributes);

        } catch (SAXException se) {

                   throw new ParserConfigurationException(se.getMessage());

         }

    }    

 }

请大家再回顾一下,JAXP的使用简单工厂和工厂方法的过程。

3.5 实现时的讨论

3.5.1 创建对象的构造方法

·使用参数化方式创建对象

·使用类别变量方式获得对象

·使用平衡类别方式创建对象

·在C++使用模板创建对象

3.5.1.1使用参数化方式创建对象

 这也是我们在简单工厂中使用的方式,通过一个变量信息,来决定创建哪个具体对象,一般使用配置信息,来达到不需修改源码。

 

public abstract class Application{   

    public abstract Document CreateDocument(int select) ;

    public void NewDocument(){

        int select ;

        Document doc = CreateDocument(select);

        doc.Open() ;

        doc.Save() ;

        doc.Close() ;

    }

}

public class MyApplication extends Application{

    public Document CreateDocument(int select){

        if (select==1) return new TextDocument();

        if (select==2) return new DocDocument();

        return null;

    }

}

3.5.1.2 使用类别变量方式获得对象

 这种方式严格上说,不算创建模式,因为它本身并没有创建对象,而使用外边传入的对象,这里列出,供大家参考。

public abstract class Application{

   protected Document doc;  //一个变量

    public Application(){

    }

    public abstract Document CreateDocument() ;

    public void NewDocument(){

        doc = CreateDocument();

        doc.Open();

        doc.Save();

        doc.Close();

    }

}

public class MyApplication extends Application{

   public MyApplication(){

    }

    public Document CreateDocument(){

        return doc;  //并没有返回新的对象而是外边传人的

    }

}

public class ExampleFactory3 {

    public static void main(String[] args){

        MyApplication App = new MyApplication();

        App.doc = new TextDocument();        //传人对象

        App.NewDocument();

    }

}

3.5.1.3 使用平衡方式创建对象

 这样如果有一个产品,就存在一个相应的工厂类,在设计模式的书中有大量的这种例子,这样造成的缺点就是使工厂类数量的增加,如果不是特殊原因,不用使用该种方式。

4 Builder模式

4.1Builder模式的引入(从重构的角度考虑)

Builder模式是为了将构建复杂对象的组装过程和它的创建部件与产品对象进行分离.注意: 是解耦组装过程和创建具体部件。过程实现使用Director,它仅关心组装部件的过程,不关心每个具体部件的创建。而Builder则是定义出创建部件的接口,然后由具体的创建者ConcreteBuilder 来实现创建不同的部件。由于在Director使用是Builder接口所以,这样就可以重用创建过程,因为不同的ConcreteBuilder,虽然创建部件不同相同,但是组装过程却相同。

我们假设,每个汽车都有4个车轮,一个发动机等部件组成,如果不使用模式,按照一般的方法,在构造函数中,分别创建4个车轮,1个发动机,并且然后进行了组装。下面以一辆Polo为例:

public class Polo{

    //在构造函数中,创建部件,然后进行组装   

    public Polo(){

       //创建部件

       车轮 obj1=new 车轮();

       //进行组装部件

       this.车轮s.add(obj1);

      

       车轮 obj2=new 车轮();

       this.车轮s.add(obj2);

       //这里省略其他两个车轮

       .................

      

       汽车发动机 objNessan=new 汽车发动机();

       this.汽车发动机=objNessan;

        

    }    

    //汽车的组成部件

    public vector 车轮s=new Vector;

   

    public 汽车发动机 汽车发动机 =null;     

    //这里省略了其他部件和功能函数

    .................

}

这就是我们没有使用Builder的方式,这样使构造函数相当的复杂。因为它负责创建具体的部件如(4个车轮,和一个发动机),然后需要进行组装成为产品Polo。如果我们再增加一种车的类型HongDa,虽然它们种类不同,具体的部件不同,但是组装的过程相同和部件的抽象(4个车轮和一个发动机)相同。 这样违背地OO的思想重用,于是我们把组装的过程,和创建部件,从产品的构造中独立出来,其实就是简化产品的构造函数,封装成对象,来重用。

   于是我们外部化一个Builder它负责创建汽车部件和组装过程,因为创建的部件不同,如Polo的车轮和HongDa的不同,但是它们的组装过程却是相同的,于是我们先定义,几个方法将由它的具体子类实现,和一个具体的方法,它负责组装过程。明白的会看出这就是模板方法(Template Method)模式。

 //它抽象了创建部件,定义一个骨架方法

 public class Builder{

   public void builder车轮(int n){};

   public void builder发动机(){};

  

   public void director构造过程(){

       this.builder车轮(4);

       this.builder车轮();

   }  

 }

 public class PoloBuilder extends Builder{

    private Polo polo=new Polo();   

    public void builder车轮(int n){

        for(int i=0;i<n;i++){

            车轮 obj1=new 车轮();

            polo.车轮s.add(obj1);

        }

    }

    public void builder发动机(){

       汽车发动机 objNessan=new 汽车发动机();

       polo.汽车发动机=objNessan;

    }

    public Polo getProduct(){

       return polo;

    }  

 }

 其实当builder模式的director角色和builder角色合并得化,它就是template method模式。抽象方法负责创建部件,具体骨架方法负责组装过程。这样具体的builder只需实现抽象方法。

  Builder目前负责创建部件和组装过程,如果我们把这两个职责, 再进行划分处理,单独一个负责组装过程,另一个负责创建部件。 因为虽然部件不同,但是过程是相同的。于是抽象出两个类。

 //该类负责创建部件,具体的由子类实现

 public class Builder{

   public void builder车轮(int n){};

   public void builder发动机(){};  

 }

 //该类实现组装过程

 public class Director{

   private Builder builder=null;

  

   public class Director(Builder builder){

     this.builder=builder;

   }

   public void 组装过程(){

       builder.builder车轮(4);

       builder.builder车轮();

   }   

 }

 在Director中使用builder接口,然后按照一定规则进行组装,大家可以

 看出这是针对接口进行编程的体现。我们看具体的builder

 public class PoloBuilder extends Builder{

    private Polo polo=new Polo();   

    public void builder车轮(int n){

        for(int i=0;i<n;i++){

            车轮 obj1=new 车轮();

            polo.车轮s.add(obj1);

        }

    }

    public void builder发动机(){

       汽车发动机 objNessan=new 汽车发动机();

       polo.汽车发动机=objNessan;

    }

    public Polo getProduct(){

       return polo;

    }  

 }

 客户端的使用,首先创建具体的builder,然后使用Director

   Builder polobuild=new  PoloBuilder();

   Director d=new Director(polobuild);

   d.组装过程();

   Product p=polobuild.getProduct();  

4.2 意图

分离复杂对象的构造和它的表现形式,从而相同的构建过程能创建不同的表现形式。这是GOF《设计模式》里介绍的,其实你可以理解为就是把创建像类似产品的过程过程和创建部件从产品的构造中分离,因而达到过程的复用。

4.3 结构和参与者

·Client创建Director对象,并用合适的Builder对象来配置。

·Director在凡是要创建产品部件时,均通知Builder对象。

·Builder处理来自于Director的请求,将部件加入到产品。

·Client从Builder中取回产品。

 

·Builder

°声明创建产品对象组件的接口

·ContreteBuilder

°通过实现Builder的接口来构建和组装产品的部件

·Director

°采用Builder的接口来创建对象

·Product

°表示创建的复杂对象。ConcreteBuilder建立了产品的内部表现,定义了组装过程

° 包括了定义其组件的类,包括将部件组长成最后结果的接口

4.4  Sample Code


版权所有:UML软件工程组织