每日一得

不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速开发
最近关心的内容:SSH,seam,flex,敏捷,TDD
本站的官方站点是:颠覆软件

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  220 随笔 :: 9 文章 :: 421 评论 :: 0 Trackbacks

#

key words: 门面模式 facade模式

一、 引子

门面模式是非常简单的设计模式。

 

二、 定义与结构

门面模式( facade )又称外观模式。 GOF 在《设计模式》一书中给出如下定义:为子系统中的一组接口提供一个一致的界面, Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

定义中提到的子系统是指在设计中为了降低复杂性根据一定的规则(比如业务、功能),对系统进行的划分。子系统中封装有一些类。客户程序在使用子系统的时候,可能会像下图一样零乱。

在上面的实现方法中,客户类紧紧地依赖在子系统的实现上。子系统发生的变化,很可能要影响到客户类的调用。而且子系统在不断优化、可重用化的重构路上,会产生更多更小的类。这对使用子系统的客户类来说要完成一个工作流程,似乎要记住的接口太多了。

门面模式就是为了解决这种问题而产生的。看看使用了门面模式后的图:

这样就减少了客户程序和子系统之间的耦合,增加了可维护性。

很明显,门面模式有三个角色组成:

1)         门面角色( facade ):这是门面模式的核心。它被客户角色调用,因此它熟悉子系统的功能。它内部根据客户角色已有的需求预定了几种功能组合。

2)         子系统角色:实现了子系统的功能。对它而言, façade 角色就和客户角色一样是未知的,它没有任何 façade 角色的信息和链接。

3)         客户角色:调用 façade 角色来完成要得到的功能。

   

   

三、 举例

Facade 一个典型应用就是进行数据库连接。一般我们在每一次对数据库进行访问,都要进行以下操作:先得到 connect 实例,然后打开 connect 获得连接,得到一个 statement ,执行 sql 语句进行查询,得到查询结果集。

       我们可以将这些步骤提取出来,封装在一个类里面。这样,每次执行数据库访问只需要将必要的参数传递到这个类中就可以了。

       有兴趣可以在你正在进行的系统中实现一下。

   

   

四、 使用环境和优点

《设计模式》给出了门面模式的使用环境:

1)         当你要为一个复杂子系统提供一个简单接口时。在上面已经描述了原因。

2)         客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性(上面也提到了)。

3)         当你需要构建一个层次结构的子系统时,使用 facade 模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过 facade 进行通讯,从而简化了它们之间的依赖关系。

以下是它的优点:

1)         它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。

2)         它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。 Facade 模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。 Facade 模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用 Facade 可以降低编译依赖性,限制重要系统中较小的变化所需的重编译工作。 Facade 模式同样也有利于简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。

3)         如果应用需要,它并不限制它们使用子系统类。因此你可以让客户程序在系统易用性和通用性之间加以选择。

 

 

  五、 java 中的门面模式

先来想想门面模式和我们已经讲过的哪个模式相像?答案就是 抽象工厂 模式 两者虽然在分类上有所区别,但是都是为了方便客户程序的使用而建立。两者的不同应该在于门面模式不仅方便了客户的使用,而且隐藏了不该让客户知道的类(这些类仅仅为子系统的其他类服务)。

但是在 java 语言中提供的包的概念已经能够很好的解决上面门面模式提到的问题。你可以把一个子系统放在一个包里面,里面要提供给外面访问的类定义为 public ,而不该公布的类就可以设计为非 public

因此,在一定程度上,门面模式在 java 中基本上可以不使用了。

标准的门面模式虽然可以不再使用,但是这种提供一个中间类或者中间方法来方便客户程序使用的思想应该值得我们来实践的

   

   

六、 总结  

门面模式从整体来看,给我的感觉是,它对于使两层之间的调用粗颗粒化很有帮助,避免了大量细颗粒度的访问。这和 SOA 中的一些观点是相同的。

   门面模式大体介绍完了。请指正:)

门面模式从整体来看,给我的感觉是,它对于使两层之间的调用粗颗粒化很有帮助,避免了大量细颗粒度的访问。这和 SOA 中的一些观点是相同的。

   门面模式大体介绍完了。请指正:)

posted @ 2006-08-29 20:46 Alex 阅读(4275) | 评论 (0)编辑 收藏

一、 引子

单例模式是设计模式中使用很频繁的一种模式,在各种开源框架、应用系统中多有应用,在我前面的几篇文章中也结合其它模式使用到了单例模式。这里我们就单例模式进行系统的学习。并对有人提出的 单例模式是邪恶的 这个观点进行了一定的分析。

 

二、 定义与结构

单例模式又叫做单态模式或者单件模式。在 GOF 书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式中的 单例 通常用来代表那些本质上具有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。

单 例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的改变对象的个数。那么怎么来实现单例模式呢?一个类的对象的产生是由类构造 函数来完成的,如果想限制对象的产生,就要将构造函数变为私有的(至少是受保护的),使得外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必 须提供一个自己的对象以及访问这个对象的静态方法。

现在对单例模式有了大概的了解了吧,其实单例模式在实现上是非常简单的 —— 只有一个角色,而客户则通过调用类方法来得到类的对象。

放上一个类图吧,这样更直观一些:

 

单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象, 多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。

 

三、 实现

在单例模式的实现上有几种不同的方式,我在这里将一一讲解。先来看一种方式,它在《 java 与模式》中被称为饿汉式。

 

public class Singleton {

// 在自己内部定义自己一个实例

// 注意这是 private 只供内部调用

private static Singleton instance = new Singleton();

// 如上面所述,将构造函数设置为私有

private Singleton(){

}  

// 静态工厂方法, 提供了一个供外部访问得到对象 的静态方法  
   public static Singleton getInstance() {
     return instance;   
   }
}

 

       下面这种方式被称为懒汉式: P

 

public class Singleton {

       // 和上面有什么不同?

private static Singleton instance = null;

// 设置为私有的构造函数

private Singleton(){

}  

// 静态工厂方法

public static synchronized Singleton getInstance() {

// 这个方法比上面有所改进
          if (instance==null)

              instance new Singleton();
          return instance;   

}

}

 

先让我们来比较一下这两种实现方式。

首先他们的构造函数都是私有的,彻底断开了使用构造函数来得到类的实例的通道,但是这样也使得类失去了多态性(大概这就是为什么有人将这种模式称作单态模式)。  

在第二种方式中,对静态工厂方法进行了同步处理,原因很明显——为了防止多线程环境中产生多个实例;而在第一种方式中则不存在这种情况。

       在第二种方式中将类对自己的实例化延迟到第一次被引用的时候。而在第一种方式中则是在类被加载的时候实例化,这样多次加载会照成多次实例化。但是第二种方式由于使用了同步处理,在反应速度上要比第一种慢一些。

       java 与模式》书中提到,就 java 语言来说,第一种方式更符合 java 语言本身的特点。

       以 上两种实现方式均失去了多态性,不允许被继承。还有另外一种灵活点的实现,将构造函数设置为受保护的,这样允许被继承产生子类。这种方式在具体实现上又有 所不同,可以将父类中获得对象的静态方法放到子类中再实现;也可以在父类的静态方法中进行条件判断来决定获得哪一个对象;在 GOF 中认为最好的一种方式是维护一张存有对象和对应名称的注册表(可以使用 HashMap 来实现)。下面的实现参考《 java 与模式》采用带有注册表的方式。

 

import java.util.HashMap;

 

public class Singleton

{

// 用来存放对应关系

       private static HashMap sinRegistry = new HashMap();

       static private Singleton s = new Singleton();

       // 受保护的构造函数

       protected Singleton()

       {}

       public static Singleton getInstance(String name)

       {

              if(name == null)

                     name = "Singleton";

              if(sinRegistry.get(name)==null)

              {

                     try{

                            sinRegistry.put(name , Class.forName(name).newInstance());

                     }catch(Exception e)

                     {

                            e.printStackTrace();

                     }     

              }

              return (Singleton)(sinRegistry.get(name)); 

       }

       public void test()

       {

              System.out.println("getclasssuccess!");      

       }

}

 

public class SingletonChild1 extends Singleton

{

       public SingletonChild1(){}

       static    public SingletonChild1 getInstance()

       {

              return (SingletonChild1)Singleton.getInstance("SingletonChild1");     

       }

       public void test()

       {

              System.out.println("getclasssuccess111!"); 

       }

}

 

java 中子类的构造函数的范围不能比父类的小,所以可能存在不守规则的客户程序使用其构造函数来产生实例。

 

四、单例模式邪恶论

看这题目也许有点夸张,不过这对初学者是一个很好的警告。单例模式在 java 中的使用存在很多陷阱和假象,这使得没有意识到单例模式使用局限性的你在系统中布下了隐患……

其实这个问题早在 2001 年的时候就有人在网上系统的提出来过,我在这里只是老生常谈了。但是对于大多的初学者来说,可能这样的观点在还很陌生。下面我就一一列举出单例模式在 java 中存在的陷阱。

 

多个虚拟机

当系统中的单例类被拷贝运行在多个虚拟机下的时候,在每一个虚拟机下都可以创建一个实例对象。在使用了 EJB JINI RMI 技术的分布式系统中,由于中间件屏蔽掉了分布式系统在物理上的差异,所以对你来说,想知道具体哪个虚拟机下运行着哪个单例对象是很困难的。

因此,在使用以上分布技术的系统中,应该避免使用存在状态的单例模式,因为一个有状态的单例类,在不同虚拟机上,各个单例对象保存的状态很可能是不一样的,问题也就随之产生。而且在 EJB 中不要使用单例模式来控制访问资源,因为这是由 EJB 容器来负责的。在其它的分布式系统中,当每一个虚拟机中的资源是不同的时候,可以考虑使用单例模式来进行管理。

 

多个类加载器

当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字节都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间( namespace )来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。

也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少见。在很多 J2EE 服务器上允许存在多个 servlet 引擎,而每个引擎是采用不同的类加载器的;浏览器中 applet 小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,等等。

       这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一般情况下不要使用存在状态的单例模式。

 

       错误的同步处理

       在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能会达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定要对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。

 

       子类破坏了对象控制

       在上一节介绍最后一种扩展性较好的单例模式实现方式的时候,就提到,由于类构造函数变得不再私有,就有可能失去对对象的控制。这种情况只能通过良好的文档来规范。

 

       串行化(可序列化)

为了使一个单例类变成可串行化的,仅仅在声明中添加“ implements Serializable ”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入 readResolve 方法。 关于这个方法的具体情况请参考《 Effective Java 》一书第 57 条建议。

其实对象的串行化并不仅局限于上述方式,还存在基于 XML 格式的对象串行化方式。这种方式也存在上述的问题,所以在使用的时候要格外小心。

 

       上面罗列了一些使用单例模式时可能会遇到的问题。而且这些问题都和 java 中的类、线程、虚拟机等基础而又复杂的概念交织在一起,你如果稍不留神……。但是这并不代表着单例模式就一无是处,更不能一棒子将其打死。它还是不可缺少的一种基础设计模式,它对一些问题提供了非常有效的解决方案,在 java 中你完全可以把它看成编码规范来学习,只是使用的时候要考虑周全些就可以了。

 

五、 题外话

抛开单例模式,使用下面一种简单的方式也能得到单例,而且如果你确信此类永远是单例的,使用下面这种方式也许更好一些。

public static final Singleton INSTANCE = new Singleton();

而使用单例模式提供的方式,这可以在不改变 API 的情况下,改变我们对单例类的具体要求。

 

六、 总结

竭尽所能写下了关于单例模式比较详细的介绍,请大家指正。
posted @ 2006-08-29 19:05 Alex 阅读(360) | 评论 (0)编辑 收藏

一、引子

装饰模式?肯定让你想起又黑又火的家庭装修来。其实两者在道理上还是有很多相像的地方。家庭装修无非就是要掩盖住原来实而不华的墙面,抹上一层华而不实的涂料,让生活多一点色彩。而墙还是那堵墙,他的本质一点都没有变,只是多了一层外衣而已。

那设计模式中的装饰模式,是什么样子呢?

 

二、定义与结构

装饰模式( Decorator )也叫包装器模式( Wrapper )。 GOF 在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator 模式相比生成子类更为灵活。

       让我们来理解一下这句话。我们来设计 这个类。假设你根据需求为 类作了如下定义:

现在,在系统的一个地方需要一个能够报警的 Door ,你来怎么做呢?你或许写一个 Door 的子类 AlarmDoor ,在里面添加一个子类独有的方法 alarm() 。嗯,那在使用警报门的地方你必须让客户知道使用的是警报门,不然无法使用这个独有的方法。而且,这个还违反了 Liskov 替换原则。

也许你要说,那就把这个方法添加到 Door 里面,这样不就统一了?但是这样所有的门都必须有警报,至少是个 哑巴 警报。而当你的系统仅仅在一两个地方使用了警报门,这明显是不合理的 —— 虽然可以使用缺省适配器来弥补一下。

       这时候,你可以考虑采用装饰模式来给门动态的添加些额外的功能。

       下面我们来看看装饰模式的组成,不要急着去解决上面的问题,到了下面自然就明白了!

1)         抽象构件角色( Component ):定义一个抽象接口,以规范准备接收附加责任的对象。

2)         具体构件角色 (Concrete Component) :这是被装饰者,定义一个将要被装饰增加功能的类。

3)         装饰角色 (Decorator) :持有一个构件对象的实例,并定义了抽象构件定义的接口。

4)         具体装饰角色 (Concrete Decorator) :负责给构件添加增加的功能。

看下装饰模式的类图:

图中 ConcreteComponent 可能继承自其它的体系,而为了实现装饰模式,他还要实现 Component 接口。整个装饰模式的结构是按照组合模式来实现的,但是注意两者的目的是截然不同的(关于两者的不同请关注我以后的文章)。

 

三、举例

这个例子还是来自我最近在研究的 JUnit ,如果你对 JUnit 还不太了解,可以参考 JUnit入门》 JUnit源码分析(一)》 JUnit源码分析(二)》 JUnit源码分析(三)》 。不愧是由 GoF 之一的 Erich Gamma 亲自开发的,小小的东西使用了 N 种的模式在里面。下面就来看看 JUnit 中的装饰模式。

       JUnit 中, TestCase 是一个很重要的类,允许对其进行功能扩展。

       junit.extensions 包中, TestDecorator RepeatedTest 便是对 TestCase 的装饰模式扩展。下面我们将它们和上面的角色对号入座。

       呵呵,看看源代码吧,这个来的最直接!

       // 这个就是抽象构件角色

       public interface Test {

       /**

        * Counts the number of test cases that will be run by this test.

        */

       public abstract int countTestCases();

       /**

        * Runs a test and collects its result in a TestResult instance.

        */

       public abstract void run(TestResult result);

}

 

// 具体构件对象,但是这里是个抽象类

public abstract class TestCase extends Assert implements Test {

       ……

       public int countTestCases() {

              return 1;

       }

       ……

       public TestResult run() {

              TestResult result= createResult();

              run(result);

              return result;

       }

       public void run(TestResult result) {

              result.run(this);

       }

       ……

}

 

// 装饰角色

public class TestDecorator extends Assert implements Test {

       // 这里按照上面的要求,保留了一个对构件对象的实例

       protected Test fTest;

 

       public TestDecorator(Test test) {

              fTest= test;

       }

       /**

        * The basic run behaviour.

        */

       public void basicRun(TestResult result) {

              fTest.run(result);

       }

       public int countTestCases() {

              return fTest.countTestCases();

       }

       public void run(TestResult result) {

              basicRun(result);

       }

       public String toString() {

              return fTest.toString();

       }

       public Test getTest() {

              return fTest;

       }

}

 

       // 具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数

public class RepeatedTest extends  TestDecorator {

    private int fTimesRepeat;

 

    public RepeatedTest(Test test, int repeat) {

           super(test);

           if (repeat < 0)

                  throw new IllegalArgumentException("Repetition count must be > 0");

           fTimesRepeat= repeat;

    }

    // 看看怎么装饰的吧

    public int countTestCases() {

           return super.countTestCases()*fTimesRepeat;

    }

    public void run(TestResult result) {

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

                  if (result.shouldStop())

                         break;

                  super.run(result);

           }

    }

    public String toString() {

           return super.toString()+"(repeated)";

    }

}

       使用的时候,就可以采用下面的方式:

TestDecorator test = new RepeatedTest(new TestXXX() , 3);

让我们在回想下上面提到的 的问题,这个警报门采用了装饰模式后,可以采用下面的方式来产生。

DoorDecorator alarmDoor = new AlarmDoor(new Door());

 

 

四、应用环境

       GOF 书中给出了以下使用情况:

1)         在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2)         处理那些可以撤消的职责。

3)         当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

来分析下 JUnit 的使用是属于哪种情况。首先实现了比静态继承更加灵活的方式,动态的增加功能。试想为 Test 的所有实现类通过继承来增加一个功能,意味着要添加不少的功能类似的子类,这明显是不太合适的。

而且,这就避免了高层的类具有太多的特征,比如上面提到的带有警报的抽象门类。

 

五、透明和半透明

       对于面向接口编程,应该尽量使客户程序不知道具体的类型,而应该对一个接口操作。这样就要求装饰角色和具体装饰角色要满足 Liskov 替换原则。像下面这样:

Component c = new ConcreteComponent();

Component c1 = new ConcreteDecorator(c);

JUnit 中就属于这种应用,这种方式被称为透明式。而在实际应用中,比如 java.io 中往往因为要对原有接口做太多的扩展而需要公开新的方法(这也是为了重用)。所以往往不能对客户程序隐瞒具体的类型。这种方式称为 半透明式

java.io 中,并不是纯装饰模式的范例,它是装饰模式、适配器模式的混合使用。

 

六、其它

采用 Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。这是 GOF 提到的装饰模式的缺点,你能体会吗?他们所说的小对象我认为是指的具体装饰角色。这是为一个对象动态添加功能所带来的副作用。

 

七、总结

       终于写完了,不知道说出了本意没有。请指正!
posted @ 2006-08-29 18:57 Alex 阅读(468) | 评论 (0)编辑 收藏

一、 引子

还记得警匪片上,匪徒们是怎么配合实施犯罪的吗?一个团伙在进行盗窃的时候,总     有一两个人在门口把风——如果有什么风吹草动,则会立即通知里面的同伙紧急撤退。也许放风的人并不一定认识里面的每一个同伙;而在里面也许有新来的小弟不认识这个放风的。但是这没什么,这个影响不了他们之间的通讯,因为他们之间有早已商定好的暗号。

呵呵,上面提到的放风者、偷窃者之间的关系就是观察者模式在现实中的活生生的例子。

 

二、 定义与结构

观察者( Observer )模式又名发布 - 订阅( Publish/Subscribe )模式。 GOF 给观察者模式如下定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

在这里先讲一下面向对象设计的一个重要原则——单一职责原则。因此系统的每个对象应该将重点放在问题域中的离散抽象上。因此理想的情况下,一个对象只做一件事情。这样在开发中也就带来了诸多的好处:提供了重用性和维护性,也是进行重构的良好的基础。

因此几乎所有的设计模式都是基于这个基本的设计原则来的。观察者模式的起源我觉得应该是在 GUI 和业务数据的处理上,因为现在绝大多数讲解观察者模式的例子都是这一题材。但是观察者模式的应用决不仅限于此一方面。

下面我们就来看看观察者模式的组成部分。

1)         抽象目标角色( Subject ):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。

2)         抽象观察者角色( Observer ):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。

3)         具体目标角色( Concrete Subject ):将有关状态存入各个 Concrete Observer 对象。当它的状态发生改变时 , 向它的各个观察者发出通知。

4)         具体观察者角色( Concrete Observer ):存储有关状态,这些状态应与目标的状态保持一致。实现 Observer 的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向 Concrete Subject 对象的引用。

放上观察者模式的类图,这样能将关系清晰的表达出来。

       可以看得出来,在 Subject 这个抽象类中,提供了上面提到的功能,而且存在一个通知方法: notify 。还可以看出来 Subject ConcreteSubject 之间可以说是使用了模板模式(这个模式真是简单普遍到一不小心就用到了)。

       这样当具体目标角色的状态发生改变,按照约定则会去调用通知方法,在这个方法中则会根据目标角色中注册的观察者名单来逐个调用相应的 update 方法来调整观察者的状态。这样观察者模式就走完了一个流程。

       在下面的例子中会更深刻的体验到这个流程的。

 

三、 举例

观察者模式是我在《 JUnit 源代码分析》中遗留的一个模式,因此这里将采用 JUnit 来作为例子。

JUnit 为用户提供了三种不同的测试结果显示界面,以后还可能会有其它方式的现实界面……。怎么才能将测试的业务逻辑和显示结果的界面很好的分离开?不用问,就是观察者模式!

下面我们来看看 JUnit 中观察者模式的使用代码:

// 下面是我们的抽象观察者角色, JUnit 是采用接口来实现的,这也是一般采用的方式。

// 可以看到这里面定义了四个不同的 update 方法,对应四种不同的状态变化

public interface TestListener {

       /**

        * An error occurred.

        */

       public void addError(Test test, Throwable t);

       /**

        * A failure occurred.

        */

       public void addFailure(Test test, AssertionFailedError t); 

       /**

        * A test ended.

        */

       public void endTest(Test test);

       /**

        * A test started.

        */

       public void startTest(Test test);

}

 

// 具体观察者角色,我们采用最简单的 TextUI 下的情况来说明( AWT Swing 对于整天做 Web 应用的人来说,已经很陌生了)

public class ResultPrinter implements TestListener {

       // 省略好多啊,主要是显示代码

……

       // 下面就是实现接口 TestListener 的四个方法

       // 填充方法的行为很简单的说

       /**

        * @see junit.framework.TestListener#addError(Test, Throwable)

        */

       public void addError(Test test, Throwable t) {

              getWriter().print("E");

       }

       /**

        * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)

        */

       public void addFailure(Test test, AssertionFailedError t) {

              getWriter().print("F");

       }

       /**

        * @see junit.framework.TestListener#endTest(Test)

        */

       public void endTest(Test test) {

       }

       /**

        * @see junit.framework.TestListener#startTest(Test)

        */

       public void startTest(Test test) {

              getWriter().print(".");

              if (fColumn++ >= 40) {

                     getWriter().println();

                     fColumn= 0;

              }

       }

}

 

来看下我们的目标角色,随便说下,由于 JUnit 功能的简单,只有一个目标—— TestResult ,因此 JUnit 只有一个具体目标角色。

// 好长的代码,好像没有重点。去掉了大部分与主题无关的信息

// 下面只列出了当 Failures 发生时是怎么来通知观察者的

public class TestResult extends Object {

       // 这个是用来存放测试 Failures 的集合

protected Vector fFailures;

// 这个就是用来存放注册进来的观察者的集合

       protected Vector fListeners;

 

       public TestResult() {

              fFailures= new Vector();

              fListeners= new Vector();

       }

       /**

        * Adds a failure to the list of failures. The passed in exception

        * caused the failure.

        */

       public synchronized void addFailure(Test test, AssertionFailedError t) {

              fFailures.addElement(new TestFailure(test, t));

              // 下面就是通知各个观察者的 addFailure 方法

              for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {

                     ((TestListener)e.nextElement()).addFailure(test, t);

              }

       }

       /**

        * 注册一个观察者

        */

       public synchronized void addListener(TestListener listener) {

              fListeners.addElement(listener);

       }

       /**

        * 删除一个观察者

        */

       public synchronized void removeListener(TestListener listener) {

              fListeners.removeElement(listener);

       }

       /**

        * 返回一个观察者集合的拷贝,当然是为了防止对观察者集合的非法方式操作了

     * 可以看到所有使用观察者集合的地方都通过它

        */

       private synchronized Vector cloneListeners() {

              return (Vector)fListeners.clone();

       }

       ……

}

 

嗯,观察者模式组成所需要的角色在这里已经全了。不过好像还是缺点什么……。呵呵,对!就是它们之间还没有真正的建立联系。在 JUnit 中是通过 TestRunner 来作的,而你在具体的系统中可以灵活掌握。

看一下 TestRunner 中的代码:

public class TestRunner extends BaseTestRunner {

       private ResultPrinter fPrinter;

public TestResult doRun(Test suite, boolean wait) {

// 就是在这里注册的

              result.addListener(fPrinter);

……

 

四、 使用情况

GOF 给出了以下使用观察者模式的情况:

1)         当一个抽象模型有两个方面 , 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

2)         当对一个对象的改变需要同时改变其它对象 , 而不知道具体有多少对象有待改变。

3)         当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之 , 你不希望这些对象是紧密耦合的。

其实观察者模式同前面讲过的桥梁、策略有着共同的使用环境:将变化独立封装起来,以达到最大的重用和解耦。观察者与后两者不同的地方在于,观察者模式中的目标和观察者的变化不是独立的,而是有着某些联系。

 

五、 我推你拉

观 察者模式在关于目标角色、观察者角色通信的具体实现中,有两个版本。一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”;观察者角色如果 想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模式被很形象的称为:拉模式——就是说变化的信息是观察者角色主动从目标角色中“拉”出 来的。

还有一种方法,那就是我目标角色“服务一条龙”,通知你发生变化的同时,通过一个参数将变化的细节传递到观察者角色中去。这就是“推模式”——管你要不要,先给你啦。

这两种模式的使用,取决于系统设计时的需要。如果目标角色比较复杂,并且观察者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适。如果目标角色比较简单,则“拉模式”就很合适啦。

 

六、 总结

大概的介绍了下观察者模式。希望能对你有所帮助。

posted @ 2006-08-29 18:08 Alex 阅读(287) | 评论 (0)编辑 收藏

一、 引子

记得一年前,我开始陆陆续续在自己的 blog 上连载《深入浅出设计模式》。其内容无出经典巨著《设计模式》之右,仅仅偶有己见,但是它记录了我学习、思考和讲述设计模式的过程。一晃,距离写成最后一片设计模式的文章已有 3 月余,我却迟迟没有对设计模式做一个总结。心想,总不能虎头蛇尾吧,于是便有了这篇文章。

 

二、 回顾 23 种设计模式

先来回顾下这 23 种经典的设计模式吧,下图给出了 GOF 对它们的分类:

 

   

    图中从两个纬度将 23 种设计模式划分为六大类:创建型类模式、创建型对象模式、结构型类模式、结构型对象模式、行为型类模式、行为型对象模式。 GOF 对这 23 种模式的划分是有一定道理的,虽然人为的类型划分,说到底还是有些牵强,但是如 GOF 所说,它至少可以帮助记忆学习。

    被分为六大块的 23 种设计模式并不是割裂开来的,很多模式的使用往往是相生相伴的,像工厂与单例,装饰与组合等等。 GOF 给出了模式间的关系详细描述如下图:

 

   

    记得曾经不止一次有人问我:这模式和那模式感觉上一样啊,有什么区别啊。同样,在很多论坛上也充满了这样的疑问。其实这是很正常的,面向对象设计、编程所能使用的方式不外乎这几种:继承、组合、封装行为、利用多态等等,所以 23 种模式中翻来覆去的使用这几种方式,看起来当然是似曾相逢。有人曾留言给我,让我着重表述这些模式之间的区别与类似。我当时也许诺会在最后写一篇总结性的文章专门讨论这个话题,但是现在我不打算这样干了。

 

三、俯瞰全局、 追踪溯源

    什么是设计模式? GOF 在书中如是说: 设计模式是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述; John Vlissides 曾说过:在设计模式中,仅有的、最重要的就是不断的反省;而我将它比作软件开发中经验积累出来的“公式”。

    通看这 23 种模式,就应了 Dennis DeBruler 曾说过的一句话:计算机科学是一门相信所有问题都可以通过多一个间接层 indirection )来解决的科学。在前面关于具体模式的文章中,我曾经不只一次的提到“中间层”。但是直到读到这句话,才使我跳出具体模式的束缚俯瞰全局。

    23 种模式似乎不再神奇,它们在解决问题上的思路是如此的相似——添加间接层。何止模式如此,正如 Dennis DeBruler 所言,我们所接触的很多技术都是采用这种手段,如: J2EE 的分层技术、重构等等。但是在具体操作上, 23 种模式中间接层的应用可大可小,无比灵活。观察者模式在动作触发端与动作执行端之间加入了目标角色层,解除了两端之间的耦合,巧妙地解决了一对多的关系;单例模式将构造方法私有化,并在使用者与构造方法之间添加一个获得唯一实例的静态方法,达到控制实例个数的目的。

    间接层应用如此广泛,得益于它能带来如下好处:共享逻辑(重用)、分离意图和实现(提高灵活性)、 隔 离变化(封装)、解耦等等。既然我们知道了间接层这么一回事,似乎我们可以不用知道设计模式也能做出像设计模式那样好的设计来。但是要记住,间接层应用过 于泛滥,则会过犹不及,它会导致简单问题复杂化、跳跃阅读难以理解等问题。那如何在设计中把握使用间接层的度呢?设计模式也许是很好的范例——你毕竟是站 在了巨人的肩上。

   

再深入一层细看,在设计模式中,广泛遵循了两条设计原则:面向接口编程,而不是实现;优先使用组合,而不是继承。这两条原则带来的好处,自然不用再说了。说到设计原则, 现在为人熟知的设计原则有很多,如: 单一职责原则( SRP )、开闭原则( OCP )、 Liskov 替换原则( LSP )、依赖倒置原则( DIP )和接口隔离原则( ISP )等等。这些原则的出现大多都早于设计模式,但同设计模式一样,是 OOD 过程中经验积累的结晶。它们在 23 种设计模式中都有体现,因此了解设计原则可以帮助你更好的分析和理解设计模式。

当然,这并不是说设计模式就是建立在设计原则的基础之上的。两者之间的关系是互相促进的。设计原则的诞生,也许会促成新的设计模式;设计模式的出现,也许会提炼出新的设计原则。

 

现在不禁要问,为什么使用设计模式。也许你的回答会是:提高设计的重用度、灵活性、可维护性等等。但是我认为更准确的回答应该是:解决系统设计中现有的问题。这便又回到了 GOF 给设计模式下的定义上了,绕了一个圈子,原来答案就这么简单。

这也就是我不想详细讲解各种模式区别的原因了,要解决的问题不一样就是它们之间最大的不同。如果还要详细分析,那它们都已写在 GOF 巨著中了——就是它们的定义、使用范围、优缺点等等。

 

四、 活学活用

是否我们必须按照这本巨著上描述的形式来使用设计模式呢?肯定不是这样的。数学物理公式在不同的条件下还会有不同的衍生式,何况这些在实践中总结的经验呢。

Martin 建 议根据问题的复杂性逐步的引入设计模式。这是个很好的建议,它避免你套用模式而带来了过度的设计,而这些过度的设计也许直到最后都派不上用场。比如,你的 系统中现在就仅有一个适配器角色,或者各个适配器角色没有什么共性,那么目标角色和适配器角色就可以合为一个,这样使得设计模式更加符合你系统的特性。

不 仅如此,做到设计模式的活学活用,我认为还要做到以解决问题为中心,将设计模式融合使用,避免为了设计而模式。当然这是建立在对各种设计模式了如指掌的情 况下。比如,有一段解析字符串的对象,而在使用它之前,还要做一些参数的判断等其他非解析操作,这时,你很快就会想起使用代理模式;但是这个代理提供的接 口可能有两三个(满足不同情形),而解析对象仅有一个接口,命名还不相同,这时你又想到了适配器模式。这时也许将两者融合成你独有的解决方案比笨拙的套用 两个模式好的多得多。

 

五、现在看模式

GOF 说过,这 23 种模式仅仅是对一般设计问题的总结。现在许多专有领域也开始出现了大量的设计模式,至少在我最了解的企业应用这个方向是这样的。其中有一部分模式仅仅是对 GOF 设计模式的再次包装。我们不妨叫 GOF 23 种设计模式为原始设计模式。

但是遗憾的是,越来越方便的框架支持,领域模型简化造成代码过程化、脚本化,使得在企业应用中很难看到原始设计模式的影子(当然还是可以看到遍地的命令模式)。比如: IoC 容器将单例、工厂、原型模式包装了起来,你现在需要做的仅仅是填写配置文件;框架集成了观察者、模版等等模式,你仅仅按照框架说明实现具体对象就可以了;过程化、脚本化的代码里面更不要提什么设计模式了!更甚者在 EJB 中单例模式差点变成了反模式。

原 始的设计模式没有用,过时了吗?如果你不甘心仅仅做一名代码组装工;想对你们部门的高手设计的框架品头论足的话,答案就是否定的。原始设计模式有很多的确 是难得一见了,但是了解它们绝对不是在浪费你的时间,它可以让你在解决问题的时候思路更开阔一些——它的思想比它的架势更重要。          

 

 

写在最后(其实应该在最前面)

    细想自己在学习设计模式时,常常埋怨《 Java 与模式》肤浅无物,为了模式而模式;又感叹 GOF 写 得高度概括,苦于理解。于是便有了将自己对设计模式的认识写下来的想法。正巧参与了部门组织的一次设计模式讲座,触发了第一篇文章的诞生。从现在来看,文 章倒是全写成了,可内容上却不能让自己满意,却又懒得动手修改(谈何容易)。“深入”二字说来容易,做到何其难,自以为这些文章的分量远远够不上;倒是 “浅出”,自以为还可以沾上点边。你可以把本系列文章看作是《 Java 与模式》的替代品,帮你叩开设计模式之门。如果你要深入研究设计模式,我劝你还是去研读《设计模式》一书吧。
posted @ 2006-08-29 17:56 Alex 阅读(218) | 评论 (0)编辑 收藏

一、引子
      
话说十年前,有一个暴发户,他家有三辆汽车 ——Benz 奔驰、 Bmw 宝马、 Audi 奥迪,还雇了司机为他开车。不过,暴发户坐车时总是怪怪的:上 Benz 车后跟司机说 开奔驰车! ,坐上 Bmw 后他说 开宝马车! ,坐上 Audi 开奥迪车! 。你一定说:这人有病!直接说开车不就行了?!
       
而当把这个暴发户的行为放到我们程序设计中来时,会发现这是一个普遍存在的现象。幸运的是,这种有病的现象在 OO (面向对象)语言中可以避免了。下面就以 Java 语言为基础来引入我们本文的主题:工厂模式。

二、分类
      
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

工厂模式在《 Java 与模式》中分为三类:
      1
)简单工厂模式( Simple Factory

2 )工厂方法模式( Factory Method

3 )抽象工厂模式( Abstract Factory
      
这三种模式从上到下逐步抽象,并且更具一般性。
       GOF
在《设计模式》一书中将工厂模式分为两类:工厂方法模式( Factory Method )与抽象工厂模式( Abstract Factory )。将简单工厂模式( Simple Factory )看为工厂方法模式的一种特例,两者归为一类。

两者皆可,在本文使用《 Java 与模式》的分类方法。下面来看看这些工厂模式是怎么来 治病 的。

 

三、简单工厂模式

简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
      
先来看看它的组成:

1)         工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在 java 中它往往由一个具体类实现。

2)         抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在 java 中由接口或者抽象类来实现。

3)         具体产品角色:工厂类所创建的对象就是此角色的实例。在 java 中由一个具体类实现。

来用类图来清晰的表示下的它们之间的关系(如果对类图不太了解,请参考我关于类图的文章):  

那么简单工厂模式怎么来使用呢?我们就以简单工厂模式来改造暴发户坐车的方式 —— 现在暴发户只需要坐在车里对司机说句: 开车 就可以了。

// 抽象产品角色

public interface Car{

public void drive();
}

 

// 具体产品角色
public class Benz implements Car{

public void drive()  {

System.out.println("Driving Benz ");

}

}

public class Bmw implements Car{

public void drive()  {

System.out.println("Driving Bmw ");

}

}
。。。(奥迪我就不写了 :P


//
工厂类角色

public class Driver{

// 工厂方法 . 注意 返回类型为抽象产品角色
       public staticCar driverCar(String s)throws Exception    {

              // 判断逻辑,返回具体的产品角色给 Client
              if(s.equalsIgnoreCase("Benz"))  

                     return new Benz();

              else if(s.equalsIgnoreCase("Bmw"))

                     return new Bmw();

                    ......   
             else throw new Exception();

       。。。


//
欢迎暴发户出场 ......

public class Magnate{

       public static void main(String[] args){

              try{
                     //
告诉司机我今天坐奔驰
              
                     Car car = Driver.driverCar("benz");
                     //
下命令:开车
                   
                     car.drive();

              。。。

    将本程序空缺的其他信息填充完整后即可运行。如果你将所有的类放在一个文件中,请不要忘记只能有一个类被声明为 public 。本程序在 jdk1.4 下运行通过。

      程序中各个类的关系表达如下:

 

这便是简单工厂模式了。怎么样,使用起来很简单吧?那么它带来了什么好处呢?
      
首先,使用了简单工厂模式后,我们的程序不在 有病 ,更加符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责 消费 产品(正如暴发户所为)。

       下 面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式。当暴发户增加了一辆车的时候,只要符合抽象产品制定的合同,那么只要通知工厂类知道 就可以被客户使用了。所以对产品部分来说,它是符合开闭原则的;但是工厂部分好像不太理想,因为每增加一辆车,都要在工厂类中增加相应的业务逻辑或者判断 逻辑,这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的。对于这样的工厂类(在我们的例子中是为司机师傅),我们称它为全能类或者上 帝类。
      
我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝累坏了,也累坏了我们这些程序员 :(
      
于是工厂方法模式作为救世主出现了。


四、工厂方法模式

       工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

你应该大致猜出了工厂方法模式的结构,来看下它的组成:

1)         抽象工厂角色:   这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现。

2)         具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。

3)         抽象产品角色:它是具体产品继承的父类或者是实现的接口。在 java 中一般有抽象类或者接口来实现。

4)         具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现。

用类图来清晰的表示下的它们之间的关系:

工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的 上帝类 。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活起来 —— 当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。可以看出工厂角色的结构也是符合开闭原则的!

       我 们还是老规矩,使用一个完整的例子来看看工厂模式各个角色之间是如何来协调的。话说暴发户生意越做越大,自己的爱车也越来越多。这可苦了那位司机师傅了, 什么车它都要记得,维护,都要经过他来使用!于是暴发户同情他说:看你跟我这么多年的份上,以后你不用这么辛苦了,我给你分配几个人手,你只管管好他们就 行了!于是,工厂方法模式的管理出现了。代码如下:

// 抽象产品角色,具体产品角色与简单工厂模式类似,只是变得复杂了些,这里略。
//
抽象工厂角色
public interface Driver{
       public Car driverCar();
}
public class BenzDriver implements Driver{
       public Car driverCar(){
              return new Benz();
       }
}
public class BmwDriver implements Driver{
       public Car driverCar()   {

return new Bmw();
       }
}

// 应该和具体产品形成对应关系 ...
//
有请暴发户先生

 public class Magnate

{

              public static void main(String[] args)

              {

                     try{ 
                            Driver driver = new BenzDriver();

                            Car car = driver.driverCar();

                            car.drive();

                     }

       ……

}

可 以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。因为如果不能避免这种情况, 可以考虑使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。

 

五、小结

工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口。那我们是否一定要在代码中遍布工厂呢?大可不必。也许在下面情况下你可以考虑使用工厂方法模式:

1)         当客户程序不需要知道要使用对象的创建过程。

2)         客户程序使用的对象存在变动的可能,或者根本就不知道使用哪一个具体的对象。

 

简 单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;而在工厂方法模式中,要么将判断 逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就象上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。

       面对这种情况, Java 的反射机制与配置文件的巧妙结合突破了限制 —— 这在 Spring 中完美的体现了出来。

 

六、抽象工厂模式

       先来认识下什么是产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族。还是让我们用一个例子来形象地说明一下吧。

 

图中的 BmwCar BenzCar 就是两个产品树(产品层次结构);而如图所示的 BenzSportsCar BmwSportsCar 就是一个产品族。他们都可以放到跑车家族中,因此功能有所关联。同理 BmwBussinessCar BenzSportsCar 也是一个产品族。
     
回到抽象工厂模式的话题上。

可以说,抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。

抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象

而且使用抽象工厂模式还要满足一下条件:

1)         系统中有多个产品族,而系统一次只可能消费其中一族产品。

2)         同属于同一个产品族的产品以其使用。

来看看抽象工厂模式的各个角色(和工厂方法的如出一辙):

1)         抽象工厂角色:   这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在 java 中它由抽象类或者接口来实现。

2)         具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在 java 中它由具体的类来实现。

3)         抽象产品角色:它是具体产品继承的父类或者是实现的接口。在 java 中一般有抽象类或者接口来实现。

4)         具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在 java 中由具体的类来实现。

类图如下:

看过了前两个模式,对这个模式各个角色之间的协调情况应该心里有个数了,我就不举具体的例子了。只是一定要注意满足使用抽象工厂模式的条件哦。
posted @ 2006-08-29 17:26 Alex 阅读(466) | 评论 (0)编辑 收藏

工厂模式定义:提供创建对象的接口.

为何使用?
工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。

为什么工厂模式是如此常用?因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。

我们以类Sample为例, 如果我们要创建Sample的实例对象:

Sample sample=new Sample();

可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值 查询数据库等。

首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成:

Sample sample=new Sample(参数);

但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(就需要Refactor重整)。

为什么说代码很难看,初学者可能没有这种感觉,我们分析如下,初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有背于Java 面向对象的原则,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码分派“切割”成每段,将每段再 “封装”起来(减少段和段之间偶合联系性),这样,就会将风险分散,以后如果需要修改,只要更改每段,不会再发生牵一动百的事情。

在本例中,首先,我们需要将创建实例的工作与使用实例的工作分开, 也就是说,让创建实例所需要的大量初始化工作从Sample的构造函数中分离出去。

这时我们就需要Factory工厂模式来生成对象了,不能再用上面简单new Sample(参数)。还有,如果Sample有个继承如MySample, 按照面向接口编程,我们需要将Sample抽象成一个接口.现在Sample是接口,有两个子类MySample 和HisSample .我们要实例化他们时,如下:

Sample mysample=new MySample();
Sample hissample=new HisSample();

随着项目的深入,Sample可能还会"生出很多儿子出来", 那么我们要对这些儿子一个个实例化,更糟糕的是,可能还要对以前的代码进行修改:加入后来生出儿子的实例.这在传统程序中是无法避免的.

但如果你一开始就有意识使用了工厂模式,这些麻烦就没有了.

工厂方法
你会建立一个专门生产Sample实例的工厂:

public class Factory{

  public static Sample creator(int which){

  //getClass 产生Sample 一般可使用动态类装载装入类。
  if (which==1)
    return new SampleA();
  else if (which==2)
    return new SampleB();

  }

}

那么在你的程序中,如果要实例化Sample时.就使用

Sample sampleA=Factory.creator(1);

这样, 在整个就不涉及到Sample的具体子类,达到封装效果,也就减少错误修改的机会,这个原理可以用很通俗的话来比喻:就是具体事情做得越多,越容易范错 误.这每个做过具体工作的人都深有体会,相反,官做得越高,说出的话越抽象越笼统,范错误可能性就越少.好象我们从编程序中也能悟出人生道理?呵呵.

使用工厂方法 要注意几个角色,首先你要定义产品接口,如上面的Sample,产品接口下有Sample接口的实现类,如SampleA,其次要有一个factory类,用来生成产品Sample,如下图,最右边是生产的对象Sample:

进一步稍微复杂一点,就是在工厂类上进行拓展,工厂类也有继承它的实现类concreteFactory了

抽象工厂
工厂模式中有: 工厂方法(Factory Method) 抽象工厂(Abstract Factory).

这两个模式区别在于需要创建对象的复杂程度上。如果我们创建对象的方法变得复杂了,如上面工厂方法中是创建一个对象Sample,如果我们还有新的产品接口Sample2.

这里假设:Sample有两个concrete类SampleA和SamleB,而Sample2也有两个concrete类Sample2A和SampleB2

那么,我们就将上例中Factory变成抽象类,将共同部分封装在抽象类中,不同部分使用子类实现,下面就是将上例中的Factory拓展成抽象工厂:

public abstract class Factory{

  public abstract Sample creator();

  public abstract Sample2 creator(String name);

}

public class SimpleFactory extends Factory{

  public Sample creator(){
    .........
    return new SampleA

  }

  public Sample2 creator(String name){
    .........
    return new Sample2A

  }

}

public class BombFactory extends Factory{

  public Sample creator(){
    ......
    return new SampleB

  }

  public Sample2 creator(String name){
    ......
    return new Sample2B
  }

}

 

从上面看到两个工厂各自生产出一套Sample和Sample2,也许你会疑问,为什么我不可以使用两个工厂方法来分别生产Sample和Sample2?

抽 象工厂还有另外一个关键要点,是因为 SimpleFactory内,生产Sample和生产Sample2的方法之间有一定联系,所以才要将这两个方法捆绑在一个类中,这个工厂类有其本身特 征,也许制造过程是统一的,比如:制造工艺比较简单,所以名称叫SimpleFactory。

在实际应用中,工厂方法用得比较多一些,而且是和动态类装入器组合在一起应用,

举例

我们以Jive的ForumFactory为例,这个例子在前面的Singleton模式中我们讨论过,现在再讨论其工厂模式:

public abstract class ForumFactory {

  private static Object initLock = new Object();
  private static String className = "com.jivesoftware.forum.database.DbForumFactory";
  private static ForumFactory factory = null;

  public static ForumFactory getInstance(Authorization authorization) {
    //If no valid authorization passed in, return null.
    if (authorization == null) {
      return null;
    }
    //以下使用了Singleton 单态模式
    if (factory == null) {
      synchronized(initLock) {
        if (factory == null) {
            ......

          try {
              //动态转载类
              Class c = Class.forName(className);
              factory = (ForumFactory)c.newInstance();
          }
          catch (Exception e) {
              return null;
          }
        }
      }
    }

    //Now, 返回 proxy.用来限制授权对forum的访问
    return new ForumFactoryProxy(authorization, factory,
                    factory.getPermissions(authorization));
  }

  //真正创建forum的方法由继承forumfactory的子类去完成.
  public abstract Forum createForum(String name, String description)
  throws UnauthorizedException, ForumAlreadyExistsException;

  ....

}

 

 

因为现在的Jive是通过数据库系统存放论坛帖子等内容数据,如果希望更改为通过文件系统实现,这个工厂方法ForumFactory就提供了提供动态接口:

private static String className = "com.jivesoftware.forum.database.DbForumFactory";

你可以使用自己开发的创建forum的方法代替com.jivesoftware.forum.database.DbForumFactory就可以.

在上面的一段代码中一共用了三种模式,除了工厂模式外,还有Singleton单态模式,以及proxy模式,proxy模式主要用来授权用户对forum的访问,因为访问forum有两种人:一个是注册用户 一个是游客guest,那么那么相应的权限就不一样,而且这个权限是贯穿整个系统的,因此建立一个proxy,类似网关的概念,可以很好的达到这个效果.  

看看Java宠物店中的CatalogDAOFactory:

public class CatalogDAOFactory {

  /**

  * 本方法制定一个特别的子类来实现DAO模式。
  * 具体子类定义是在J2EE的部署描述器中。
  */

  public static CatalogDAO getDAO() throws CatalogDAOSysException {

    CatalogDAO catDao = null;

    try {

      InitialContext ic = new InitialContext();
      //动态装入CATALOG_DAO_CLASS
      //可以定义自己的CATALOG_DAO_CLASS,从而在无需变更太多代码
      //的前提下,完成系统的巨大变更。

      String className =(String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);

      catDao = (CatalogDAO) Class.forName(className).newInstance();

    } catch (NamingException ne) {

      throw new CatalogDAOSysException("
        CatalogDAOFactory.getDAO: NamingException while
          getting DAO type : \n" + ne.getMessage());

    } catch (Exception se) {

      throw new CatalogDAOSysException("
        CatalogDAOFactory.getDAO: Exception while getting
          DAO type : \n" + se.getMessage());

    }

    return catDao;

  }

}


CatalogDAOFactory 是典型的工厂方法,catDao是通过动态类装入器className获得CatalogDAOFactory具体实现子类,这个实现子类在Java宠物 店是用来操作catalog数据库,用户可以根据数据库的类型不同,定制自己的具体实现子类,将自己的子类名给与CATALOG_DAO_CLASS变量 就可以。

由此可见,工厂方法确实为系统结构提供了非常灵活强大的动态扩展机制,只要我们更换一下具体的工厂方法,系统其他地方无需一点变换,就有可能将系统功能进行改头换面的变化。

posted @ 2006-08-29 17:13 Alex 阅读(214) | 评论 (0)编辑 收藏

key words:正则表达式  模式匹配 javascript

摘要:收集一些常用的正则表达式。

正则表达式用于字符串处理,表单验证等场合,实用高效,但用到时总是不太把握,以致往往要上网查一番。我将一些常用的表达式收藏在这里,作备忘之用。本贴随时会更新。

匹配中文字符的正则表达式: [\u4e00-\u9fa5]

匹配双字节字符(包括汉字在内):[^\x00-\xff]

应用:计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}

匹配空行的正则表达式:\n[\s| ]*\r

匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/

匹配首尾空格的正则表达式:(^\s*)|(\s*$)

应用:javascript中没有像vbscript那样的trim函数,我们就可以利用这个表达式来实现,如下:

String.prototype.trim = function()

{
    return this.replace(/(^\s*)|(\s*$)/g, "");
}

利用正则表达式分解和转换IP地址:

下面是利用正则表达式匹配IP地址,并将IP地址转换成对应数值的javascript程序:

function IP2V(ip)
{
 re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g  //匹配IP地址的正则表达式
if(re.test(ip))
{
return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1
}
else
{
 throw new Error("Not a valid IP address!")
}
}

不过上面的程序如果不用正则表达式,而直接用split函数来分解可能更简单,程序如下:

var ip="10.100.20.168"
ip=ip.split(".")
alert("IP值是:"+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1))

匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*

匹配网址URL的正则表达式:http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?

得用正则表达式从URL地址中提取文件名的javascript程序,如下结果为page1

s="http://www.9499.net/page1.htm"
s=s.replace(/(.*\/){0,}([^\.]+).*/ig,"$2")
alert(s)

利用正则表达式限制网页表单里的文本框输入内容:

用正则表达式限制只能输入中文:onkeyup="value=value.replace(/[^ \u4E00-\u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"

用正则表达式限制只能输入全角字符:  onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"

用正则表达式限制只能输入数字:onkeyup="value=value.replace(/[^ \d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"

用正则表达式限制只能输入数字和英文:onkeyup="value=value.replace (/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"

"^\\d+$"  //非负整数(正整数 + 0)
"^[0-9]*[1-9][0-9]*$"  //正整数
"^((-\\d+)|(0+))$"  //非正整数(负整数 + 0)
"^-[0-9]*[1-9][0-9]*$"  //负整数
"^-?\\d+$"    //整数
"^\\d+(\\.\\d+)?$"  //非负浮点数(正浮点数 + 0)
"^(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$"  //正浮点数
"^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$"  //非正浮点数(负浮点数 + 0)
"^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"  //负浮点数
"^(-?\\d+)(\\.\\d+)?$"  //浮点数
"^[A-Za-z]+$"  //由26个英文字母组成的字符串
"^[A-Z]+$"  //由26个英文字母的大写组成的字符串
"^[a-z]+$"  //由26个英文字母的小写组成的字符串
"^[A-Za-z0-9]+$"  //由数字和26个英文字母组成的字符串
"^\\w+$"  //由数字、26个英文字母或者下划线组成的字符串
"^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"    //email地址
"^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$"  //url

"^[A-Za-z0-9_]*$"

posted @ 2006-08-29 15:02 Alex 阅读(576) | 评论 (1)编辑 收藏

     摘要: key words:正则表达式 深入浅出之正则表达式(一) 前言:        半年前我对正则表达式产生了兴趣,在网上查找过...  阅读全文
posted @ 2006-08-28 23:21 Alex 阅读(707) | 评论 (0)编辑 收藏

     摘要: 本程序最初是由wanghr100(灰豆宝宝.net)的checkForm基础上进行修改的,增加了很多功能,如下: 对非ie的支持 增加了内置表达式和内置提示 增加了显示方式(弹出式和页面显示式) 增加了显示一条和显示全部 进行了封装(CLASS_CHECK) 支持外接函数或表达式(应用在密码一致) ...  阅读全文
posted @ 2006-08-28 22:20 Alex 阅读(497) | 评论 (0)编辑 收藏

反向工程生成的PDM,表中NAME和CODE都一样,现在要修改NAME.
在修改一个表的NAME属性时,CODE属性也会跟着一起改变,怎么样才能让CODE不跟着NAME变呢?



Tools->General Options->Dialog->Name to Code Mirroring (去掉)
posted @ 2006-08-26 23:04 Alex 阅读(725) | 评论 (0)编辑 收藏

在应用中有这样一个情况,
在A窗口中打开B窗口,在B窗口中操作完以后关闭B窗口,同时自动刷新A窗口


function closeWin(){
        hasClosed 
= true;
        window.opener.location
="javascript:reloadPage();";
        window.close();
    }
    
function window.onbeforeunload(){
        
if(!hasClosed){
            window.opener.location
="javascript:reloadPage();";
        }
    }

</script>

上面的代码在关闭B窗口的时候会提示错误,说缺少Object,正确的代码如下:
function closeWin(){
        hasClosed 
= true;
        window.opener.location
="javascript:reloadPage();";
        window.opener
=null;
        window.close();
    }
    
function window.onbeforeunload(){
        
if(!hasClosed){//如果已经执行了closeWin方法,则不执行本方法
            window.opener.location
="javascript:reloadPage();";
        }
    }

</script>

reloadPage方法如下:
function reloadPage() {
        history.go(
0);
        document.execCommand(
"refresh")
        document.location 
= document.location;
        document.location.reload();
    }

PS:由于需要支持正常关闭和强制关闭窗口时能捕捉到事件,用了全局变量hasClosed

==============================================

补充,在父窗口是frame的时候在刷新父窗口的时候会出现问题:

The page cannot be refreshed without resending the information.

后修改如下:
window.opener.parent.document.frames.item('mainFrame').location.href = window.opener.location.href;

不需要执行自带的reload()方法,注意,不要再画蛇添足加上这一句:

window.opener.parent.document.frames.item('mainFrame').location.reload();

========================================================================================
最后,为了同时支持刷新普通父窗口和frame父窗口,代码如下:
function closeWin() {
        hasClosed 
= true;
    
<%if(null != frame){%>
        window.opener.parent.document.frames.item('mainFrame').location.href 
= window.opener.location.href;
    
<%}else{%>
        window.opener.location 
= "javascript:reloadPage();";
    
<%}%>
        
//window.opener.top.mainFrame.location="javascript:reloadPage();";
        //self.opener.frames.mainFrame.location.reload(true);
        window.opener = null;
        window.close();
    }
    
function window.onbeforeunload(){
        
if (!hasClosed) {
        
<%if(null != frame){%>
            window.opener.parent.document.frames.item('mainFrame').location.href 
= window.opener.location.href;
        
<%}else{%>
            window.opener.location 
= "javascript:reloadPage();";
        
<%}%>
            window.opener 
= null;
        }
    }




posted @ 2006-08-23 10:54 Alex 阅读(3737) | 评论 (0)编辑 收藏

key words:select text值
获得select的value很容易,但是如何获得其text的内容呢,下面的代码借助了prototype.js库:
<script language="javascript">
        
function getSelect(selectName){
        
var options = $(selectName).getElementsByTagName('option');
        options 
= $A(options);
        
var opt = options.find( function(employee){
            
return (employee.value == $F(selectName));
        });
        alert(opt.innerHTML);
        }
    
</script>

<select name="hi" onchange="getSelect('hi');">
                
<option value="01"></option>
                
<option value="02"></option>
                
<option value="03"></option>
                
<option value="04"></option>
</select>

PS: $A : 转换成数组格式
    opt.innerHTML:nodeText
posted @ 2006-08-21 22:58 Alex 阅读(6816) | 评论 (1)编辑 收藏

key words: 锁表 解锁
查询sql:
SELECT A.OWNER,
A.
OBJECT_NAME,
B.XIDUSN,
B.XIDSLOT,
B.XIDSQN,
B.SESSION_ID,
B.ORACLE_USERNAME, 
B.OS_USER_NAME,
B.PROCESS, 
B.LOCKED_MODE, 
C.MACHINE,
C.STATUS,
C.SERVER,
C.SID,
C.SERIAL#,
C.PROGRAM
FROM ALL_OBJECTS A,
V$LOCKED_OBJECT B,
SYS.GV_$SESSION C 
WHERE ( A.OBJECT_ID = B.OBJECT_ID )
AND (B.PROCESS = C.PROCESS )
ORDER BY 1,2 

释放session Sql:
alter system kill session 'sid, serial#'

alter system kill session '379, 21132'
alter system kill session '374, 6938'

posted @ 2006-08-21 21:58 Alex 阅读(15498) | 评论 (2)编辑 收藏

key words: 在子窗口中如何调用父窗口的方法,属于"回调",直接拿到父窗口的句柄,然后直接调用方法不行,比如:
parent.window.show(b)
window.opener.show(b)

都不行,正确的做法是:
opener.location="javascript:show('hello')";

PS: 直接调用父窗口的对象与此不同,参考这篇: 用javaScript操作两个页面

posted @ 2006-08-21 21:55 Alex 阅读(18421) | 评论 (3)编辑 收藏

仅列出标题
共15页: First 上一页 5 6 7 8 9 10 11 12 13 下一页 Last