此前写的一个Design Pattern的文章

以前写的一个Design Pattern的文章
Design Pattern Practice
1.序
本文从一个简单的多列排序的例子入手,由浅入深地讲解Design Pattern(设计模式)的目的、分析和实践。
文中的例子用到Compositor Pattern和Proxy Pattern。
同时,文中的例子也提供了一类问题(条件组合问题)的解决方案。

2.问题的引入
Design Pattern(设计模式)的目标是,把共通问题中的不变部分和变化部分分离出来。不变的部分,就构成了Design Pattern(设计模式)。这一点和Framework(框架)有些象。
下面举个排序的例子,说明如何抽取问题中的不变部分。
假设一个Java类Record有field1,field2,field3等字段。
public class Record{
	public int field1;
	public long field2;
	public double filed3;
};


我们还有一个Record对象的数组Record[] records。我们需要对这个数组按照不同的条件排序。
首先,按照field1的大小从小到大进行升序排序。
排序函数如下:
void sort(Record[] records){
	for(int i =….){
		for(int j=….){
			if(records[i].field1 > records[j].field1)
				// swap records[i] and records[j]
		}
	}
}


其次,按照field2的大小从小到大进行升序排序。
void sort(Record[] records){
	for(int i =….){
		for(int j=….){
			if(records[i].field2 > records[j].field2)
				// swap records[i] and records[j]
		}
	}
}


再次,按照field3的大小从小到大进行升序排序。
这种要求太多了,我们写了太多的重复代码。我们可以看到,问题的变化部分,只有判断条件部分(黑体的if条件判断语句)。
我们可以引入一个Comparator接口,把这个变化的部分抽取出来。
public interface Comparator(){
	public boolean greaterThan(Record a, Record b);
};
sort函数就可以这样写(把判断条件作为参数):
void sort(Record[] records, Comparator compare){
	for(int i =….){
		for(int j=….){
			if(compare.greaterThen(records[i], records[j]))
				// swap records[i] and records[j]
		}
	}
}


这样,对应第一个要求——对records数组按照field1的大小排序。
我们可以做一个实现Comparator接口的CompareByField1类。
public class CompareByField1 implements Comparator{
public boolean greaterThan(Record a, Record b){
	if(a.filed1 > b.filed1){
		return ture;
	}
	return false;
}
}


sort函数的调用为:sort(records, new CompareByField1());

这样,对应第一个要求——对records数组按照field2的大小排序。
我们可以做一个实现Comparator接口的CompareByField2类。

public class CompareByField2 implements Comparator{
public boolean greaterThan(Record a, Record b){
	if(a.filed2 > b.filed2){
		return ture;
	}
	return false;
}
}

sort函数的调用为:sort(records, new CompareByField2());
按照C++ STL的叫法,这里的sort称为算法(Algorithm),records称为容器(集合),Comparator称为函数对象(Function Object)。

JDK的java.util.Collections类的sort方法和java.util.Comparator接口就是按照这样的思路设计的。下面我们来看看如何应用sort和Comparator解决多列排序问题。

3.多列排序问题
3.1排序条件的数量
我们知道,SQL语句能够实现强大的排序功能,能够按照不同字段的排列进行排序,也能够按照升序,降序排序。比如下面的语句。
order by field1 asc, field2 asc, field3 desc。

这个排序条件按照field1的升序,field2的升序,field3的降序排序。
注意,排在前面的字段具有较高的优先级。
比如,两条纪录A和B,满足如下条件:(1)A.field1 > B.field1,(2)A.field2 < B.field2。
这时如果按照order by field1, field2语句排序,那么 A > B。
如果上述条件中的(1)A.field1 > B.field1变化为A.field1 == B.field1。这时,条件(2)就会起作用。这时,A < B。

我们来看看在Java中如何实现这种灵活而强大的排序。
我们还是以上一节的Record类为例。Record类有3个字段,我们来看一看,有多少种可能的排序条件。
(1)按field1排序。(2)按field2排序。(3)按field3排序。(4)按field1,field2排序。(5)按field1升序,按field2降序排序…...

各种排序条件的排列组合,大概共有30种。而且,随着字段个数的增长,排序条件的个数呈幂级数的增长。
按照上一节的sort和Comparator方法,如果我们需要达到按照任意条件进行排序的目的,那么我们需要为每一个排序条件提供一个Comparator,我们需要30个Comparator类。:-)
当然,我们不会这么做,我们能够进一步提取这个问题中的相同重复部分,优化我们的解决方案。

3.2 问题分析
我们来分析这个问题中变化的部分和不变的部分。
上面所有的排序条件中,不变的部分有3部分:(1)A.field1和B.field1的比较,(2)A.field2和B.field2的比较,(3)A.field3和B.field3的比较;变化的部分有两部分,(1)这三种比较条件的任意组合排列,(2)升序和降序。
根据这段分析,我们引入两个类,ReverseComparator类和CompositeComparator类。
CompositeComparator类用来解决字段的组合排列问题。
ReverseComparator类用来解决字段的升序、降序问题。

3.3 ReverseComparator类的代码

import java.util.Comparator;

public class ReverseComparator implements Comparator{
  /** the original comparator*/
  private Comparator originalComparator = null;

  /** constructor takes a comparator as parameter */
  public ReverseComparator(Comparator comparator){
    originalComparator = comparator;
  }

  /** reverse the result of the original comparator */
  public int compare(Object o1, Object o2){
    return - originalComparator.compare(o1, o2);
  }
}


3.4 CompositeComparator类的代码

import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;

public class CompositeComparator implements Comparator{
  /** in the condition list, comparators' priority decrease from head to tail */
  private List comparators = new LinkedList();

  /** get the comparators, you can manipulate it as need.*/
  public List getComparators(){
    return comparators;
  }

  /** add a batch of comparators to the condition list */
  public void addComparators(Comparator[] comparatorArray){
    if(comparatorArray == null){
      return;
    }

    for(int i = 0; i < comparatorArray.length; i++){
      comparators.add(comparatorArray[i]);
    }
  }

  /** compare by the priority */
  public int compare(Object o1, Object o2){
    for(Iterator iterator = comparators.iterator(); iterator.hasNext();){
      Comparator comparator = (Comparator)iterator.next();

      int result = comparator.compare(o1, o2);

      if(result != 0){
        return result;
      }
    }

    return 0;
  }
}


3.5 Comparator的组合应用
这一节讲述上面两个类的用法。
对应前面的排序问题,我们只需要3个Comparator类:
(1)Field1Comaprator;
(2)Field2Comaprator;
(3)Field3Comaprator。

下面举例说明,如何组合这些Comparator实现不同的排序条件。
(1)order by field1, field2

 CompoiComparator myComparator = new CompoiComparator();
 myComparator. addComparators(
new Comparator[]{new Field1Comaprator (),  new Field2Comaprator ()};
);

// records is a list of Record
 Collections.sort(records,  myComparator);


(2)order by field1 desc, field2

 CompoiComparator myComparator = new CompoiComparator();
 myComparator. addComparators(
new Comparator[]{
new ReverseComparator(new Field1Comaprator ()),  
new Field2Comaprator ()};
);

// records is a list of Record
 Collections.sort(records,  myComparator);


这里提供的ReverseComparator类和CompositeComparator类都采用了Decorator Pattern。
CompositeComparator类同时也是Composite Pattern。

4.过滤条件的排列组合
(多谢shinwell指正,我改正了后面的代码)

过滤条件问题也属于条件组合问题的范畴。比如JDK提供的java.io.File类提供了一个文件过滤方法listFile(FileFilter),用户可以定制不同的FileFilter,实现不同的过滤条件,比如文件时间在某个范围内;文件后缀名,文件名符合某种模式;是目录,还是文件,等等。
同样,我们可以应用上述的解决方法,实现灵活的过滤条件组合——用一个CompositeFilter类任意组合过滤条件,用一个ReverseFilter类作为排除条件。
4.1 CompositeFilter类的代码

import java.io.FileFilter;
import java.io.File;

import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;

public class CompositeFilter implements FileFilter {

  /** in the filter list, every condition should be met. */
  private List filters = new LinkedList();

  /** get the filters, you can manipulate it as need.*/
  public List getFilters(){
    return filters;
  }

  /** add a batch of filters to the condition list */
  public void addFilters(FileFilter[] filterArray){
    if(filterArray == null){
      return;
    }

    for(int i = 0; i < filterArray.length; i++){
      filters.add(filterArray[i]);
    }
  }

  /** must meet all the filter condition */
  public boolean accept(File pathname) {
    for(Iterator iterator = filters.iterator(); iterator.hasNext();){
      FileFilter filter = (FileFilter)iterator.next();

      boolean result = filter.accept(pathname);

      // if any condition can not be met, return false.
      if(result == false){
        return false;
      }
    }

    // all conditions are met, return true.
    return true;
  }
}


4.2 ReverseFilter类的代码
import java.io.FileFilter;
import java.io.File;

public class ReverseFilter implements FileFilter {
  /** the original filter*/
  private FileFilter originalFilter = null;

  /** constructor takes a filter as parameter */
  public ReverseFilter(FileFilter filter){
    originalFilter = filter;
  }

  /** must meet all the filter condition */
  public boolean accept(File pathname) {
      return !originalFilter.accept(pathname);
  }
}


5.总结
本文讲述了Design Pattern的分析和实践,并阐述了一类条件组合问题的解决思路。

Enjoy it. :-)
Thanks.
1 楼 taya 2005-06-27  
非常好的文章,多谢分享。

最近正好在重学设计模式,开始有点领悟到底该学些么了。
我觉的模式本身倒是次要的,主要的是学习怎么分析变化点,然后把变化点隐藏(封装)起来。
2 楼 intolong 2005-06-28  
我觉得一切模式归结起来就是“封装变化”
不知是否说的绝对了

文章的确好。
3 楼 幸运之星 2005-08-07  
我看了这篇文章之后, 我觉得自己学习设计模式没学到家, 真是自谈不如.现在我想提一个问题: 你这里的Decorator Pattern我就没怎么看到在哪里体现出现了,按照我的理解: Decorator Pattern这个模式既然是修饰模式, 那么也就是对一个类或者一个方法加以辅助功能(如: 权限检查, 日志记入)使它更好地完成所要的功能.

另外我想向你请教一下代理模式.
4 楼 gigix 2005-08-08  
看了一段时间LISP之后,一个感想就是:一大半的面向对象设计模式都出自同一个原因,即操作不是一等公民。为了解决操作不是一等公民带来的麻烦,而发明出了这么些个work-around。
5 楼 buaawhl 2005-08-08  
幸运之星 写道
我看了这篇文章之后, 我觉得自己学习设计模式没学到家, 真是自谈不如.现在我想提一个问题: 你这里的Decorator Pattern我就没怎么看到在哪里体现出现了,按照我的理解: Decorator Pattern这个模式既然是修饰模式, 那么也就是对一个类或者一个方法加以辅助功能(如: 权限检查, 日志记入)使它更好地完成所要的功能.

另外我想向你请教一下代理模式.


我的用词不是很准确。
Decorator, Proxy, Delegate等这些Pattern 我分别不出来,也没有花时间区分。都是一个类,包装另一个类,并引入新的特性。
"幸运之星" 如果愿意并有兴趣做一个细分,当然是求之不得的。我想,大家都愿闻其详,获取更扎实细密的基本功,于细微之处见文章。
那个ReverseComparator, CompositerComparator可以看作 包装了另外的 Comparator。

Design Pattern如此的时髦,就那么点东西,谁都想弄出几个变种(只有细节不同)来赚取眼球人气和金钱。很多细节的东西,我都已经无力追逐了。

Design Pattern这个东西。当时学习的时候,很激动。因为这帮我真正理解了OO。之前,我虽然对C++语法很熟悉,但是对OO到底有什么好处,心理一点都没有底。
后面的感觉就是,Design Pattern更多的作用是作为一种交流互通的词汇。比如,我们说Decorator, Proxy, Delegate,我们就知道这块是在包装,但是暴露的interface不变;我们说Facade,我们就知道原来暴露的interface不是恰好需要的,我们要包装成另外的interface;我们说Adaptor,我们就知道它遇到了麻烦,需要和其他代码兼容;

另外,就是作为一种 不变与变化 的分析抽象的方法。

我对Design Pattern的理解也就限于此。写这篇文章的目的,是为了突现OO语法在处理问题的时候,相对于 过程语言( C 等 ) 的优势。

gigix 写道

看了一段时间LISP之后,一个感想就是:一大半的面向对象设计模式都出自同一个原因,即操作不是一等公民。为了解决操作不是一等公民带来的麻烦,而发明出了这么些个work-around。


LISP等函数式语言,比OO语言,是更高级的语言。
函数式语言大多数又是解释性语言,动态语言,比编译语言来说,也是更高级的语言。
语法精炼、简单许多。

Java的Functor 是用Class模拟 函数式语言的 Function。
OOP: 一个Interface的method能够返回另一个Interface。
FP: 一个函数能够返回另一个函数。

如果FP来解决文章中所阐述的问题。
同样需要用另一个Function来包装原来的Fuction。
define Reverse f a b =  -  f a b

define Compose fList a b =
    iterate f in fList
        cond  f a b
              fase : return false
              true : next

我对 LISP语法不熟,上面的写法不正确。哪位指正一下。
6 楼 幸运之星 2005-08-08  
Compositor是组合模式, 就是把一些具有不同功能的类组合起来成一个类或者数组,(现在我这里假设组合成一个数组) 然后通过调用某个方法来循环数组中的所有元素,最后返回用户所需要的结果.

proxy是代理模式, 就是一个项目中的某些核心类不用给客户直接操作, 但是用户又确实需要这些功能, 那么就想到了用一个新类类包装这些核心类,其新类中的方法一般同核心类中的方法声明相同, 只是其方法的实现是通过调用核心类在新类中的实例来完成功能和用户需求。需注意一点: 不会添加各外的功能和处理。

Decorator 是修饰模式,其实就是在代理模式的基础上添加一些格外代码来实现一些附加的功能, 但不是主要功能, 一般实现如: 对参数的修改,记入日志等一些功能。
7 楼 donnie_yang 2005-08-10  
Decorator : 遵循原有接口的基础上, 扩展原有接口(而非具体实现)的功能.
8 楼 taya 2005-08-11  
个人感觉Decorator模式的引入其主要目的并不是为了扩展原有接口,而是为了避免因封装一个变化点而生成过多的子类,而子类之间又有太多重复的内容,所以才弄了这个模式,使重复的内容可以分开,然后一个个无序叠加起来使用。

呵呵,感觉自己的表达有点问题,不知道大家是否能看懂我想表达的意思。
9 楼 javavsnet 2005-08-18  
intolong 写道
我觉得一切模式归结起来就是“封装变化”
不知是否说的绝对了

文章的确好。


我的理解更浅,觉得一切模式归结起来就是“解耦”
不管多复杂的问题,先解耦,然后就清晰了
10 楼 buaawhl 2005-08-18  
javavsnet 写道
intolong 写道
我觉得一切模式归结起来就是“封装变化”
不知是否说的绝对了

文章的确好。


我的理解更浅,觉得一切模式归结起来就是“解耦”
不管多复杂的问题,先解耦,然后就清晰了


或者说,移耦。(耦合 = 相互依赖)
耦合从根本上来说是解不掉的。因为模块之间必然要相互作用。
我们只能尽量把 耦合 移动到一个独立的容易管理的地方。比如说,移动到接口层次上。

我觉得,设计模式主要还是 分离变化点。更多的是处理“多态”的问题。即存在2个,或2个以上的实现方案,或者业务规则,或者组合方式。

比如,文中的例子,正是因为 有多种比较过滤条件,才需要把比较过滤条件分离出来。
如果只是一种比较过滤条件(就是比较 field1 的大小),那么根本不用分离。
11 楼 age0 2005-08-19  
无论如何封装,该变的东西还是要变,无论如何解耦,只要关系还存在,藕合就不可能消失,相对于“封装变化”或“解藕”,“移藕”确实更加贴切。模式所做的事情主要是封装不变的对象组织关系,而将抽象与实现以多态的方式分离。
12 楼 javavsnet 2005-08-19  
移耦表达的更准确,的确是转移耦合