《编程机制探析》第八章 Compositor Pattern

《编程机制探析》第八章 Compositor Pattern

在程序设计过程中,设计模式并不一定是单独使用的,很多情况下,我们可能同时组合应用多个设计模式,从而构建成一个更复杂的设计模式。当然,这样构建出来的设计模式,通常已经失去了通用性。
在前面的章节中,我们用sort排序算法作为例子,讲解了最简单的Visitor(Functor)Pattern。那个排序的例子可以进一步扩展,引入更多的设计模式——比如,Compositor Pattern(组合模式)。
在扩展排序例子之前,我们先花一点时间,了解一下关系数据库的相关知识。
关系数据库是现在应用最为广泛的一类数据库,其优点在于概念简单,模型简单,便于操作。从概念模型上来讲,关系数据库就是一堆二维数据表的集合。什么叫做二维数据表?就是纵横排列的一群数据。
你随便打开电脑上一个电子表格程序,比如,Excel等,你看到的表格全都是二维的。为什么?因为你的电脑屏幕就是一个平面,上面最多只能显示二维的信息。至于超出的维度,比如三维、四维什么的,也只能映射到二维的平面上进行显示。
由于关系数据库里面的表格全都是二维的,有时候,关系数据库也被叫做二维关系数据库,关系数据表也被叫做二维关系数据表。别看关系数据表只有二维,但是,多个关系数据表关联起来,能够表达任意多维度的数据关系。从数学模型上来说,关系数据库与其他结构更加复杂的数据库,在表达能力上是一样的。
关系数据库是应用开发中非常重要的组成部分,是应用开发人员必须掌握的一门技术。不过,普及关系数据库知识,并不是本书的内容。关于更多的关系数据库的基本概念和模型,请读者自行补充。
关系数据库由于模型概念简单,操作起来也甚为方便。关系数据库有一门标准的查询语言,叫做SQL,是Structured Query Language(结构化查询语言)的缩写。
SQL是比普通高级编程语言——如Java、Python、Ruby等——更为接近自然语言的一门高级语言,其语法更接近于英语。SQL中最主要的语句就是select语句。通过这个语句,我们可以从关系数据库中查询符合条件的相关数据,并对查询出来的数据结果进行排序。一条带有排序语句的select语句看起来大概是这个样子的:
Select * from … where … order by field1 asc, field2 asc, field3 desc
其中,asc是ascending的缩写,表示从小到大(升序)排序;desc是descending的缩写,表示从大到小(降序)排序。上述语句的排序要求就是——按照field1的升序,field2的升序,field3的降序排序。需要注意的是,这三个排序条件是优先级的。即先按field1的升序排序,如果几个元素的field1相同,这几个元素再根据field2的升序排序。后面的排序条件以此类推,当几个元素的field2相同,这几个元素再根据field3的降序排序。
我们来看一看,如何用sort算法框架和Comparator回调来实现这么复杂的排序。上一章的例子中,Record类中恰好有field1、field2、field3三个字段。我们可以构造一个Comparator的实现类,实现如下的逻辑:先比较field1,如果不相等,那么返回比较结果;如果field1相等,那么比较field2,如果不相等,那么返回比较结果;如果field2相等,那么比较field3,并返回比较结果。
这样的解决方案,看起来很直观,也很简单。但是,这就是最终的解决方案了吗?如果我们有更多的排序条件组合呢?Record类有三个字段,每个字段都可以按照升序降序来排列,根据排列组合原理,可能的排序条件组合多达几十种:
(1)按field1排序。(2)按field2排序。(3)按field3排序。(4)按field1,field2排序。(5)按field1升序,按field2降序排序…...
如果我们为每一种条件组合都写一个Comparator的实现类,那么,我们将需要几十个这样的类。这还是只有三个字段的情况下,如果字段个数继续增长,排序条件的个数将呈幂级数的增长。显然,为每一种条件组合写一个Comparator是不现实的。我们必须为这种组合条件排序找到一种通用的解决方法。我们来所有分析条件组合中变化的部分和不变的部分。
上面所有的排序条件中,不变的部分有3部分:(1)A.field1和B.field1的比较,(2)A.field2和B.field2的比较,(3) A.field3和B.field3的比较。
变化的部分有两部分:(1)升序和降序,(2)这三种比较条件的任意组合排列。
根据这段分析,我们引入两个类,ReverseComparator类和CompositeComparator类。ReverseComparator类用来解决字段的升序、降序问题。
CompositeComparator类用来解决字段的组合排列问题。
请注意,下面的Java代码是可以编译运行的代码。我用的是Java标准开发包中java.util包的Collections中的sort(List, Comparator)过程作为算法框架。相应的,我实现的Comparator也是Java标准开发包中的java.util.Comparator接口。
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);
}
}

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;
else
return 0;
} // end for
}
这两段代码有点长,我很抱歉。值得庆幸的是,这两段代码不算太长,还不至于占据太多的篇幅。
除了这两个包含类之外,我们还需要三个基本类——Field1Comaprator、Field2Comaprator、Field3Comaprator,用来做field1、field2、field3的比较。这三个类的功能一目了然,就不再写出来了。
利用这三个基本类和两个包含类,共五个类,我们就可以任意进行条件组合了。下面举例说明,如何组合这些Comparator实现不同的排序条件。
例1:order by field1 asc, field2 asc
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 asc
CompoiComparator myComparator = new CompoiComparator();
myComparator. addComparators(
new Comparator[]{
new ReverseComparator(new Field1Comaprator ()),
new Field2Comaprator ()
};
);

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

其余的条件组合,可以类推。在上述的例子中,我们又引入了两个简单的Design Pattern——Proxy Pattern(代理模式)和Composite Pattern(组合模式)。
ReverseComparator只包含一个Comparator对象,并保持了Comparator的接口方法。这种模式叫做Proxy Pattern(代理模式,有时候也叫做Delegate Pattern),其实就是把内部对象包装了一下,并执行了一些自己的操作。在这里,ReverseComparator就是把内部对象的比较方法给反转了一下。另外有一种Design Pattern叫做Decorator Pattern,其作用与Proxy Pattern类似,只不过是用继承来实现的。前面讲过,用继承来实现重用,实际上就固定了被重用的类,失去了多态性。我们不推荐。而且,Proxy Pattern完全能够做Decorator Pattern的工作,因此,我们不需要考虑Decorator Pattern,只需要知道Proxy Pattern这个简单、常用、又非常重要的的设计模式就行了。
CompositeComparator是Composite Pattern,能够把一堆基本的简单的功能块结合在一起,构成一个接口相同、功能却复杂得多的功能块。
Composite Pattern能够在各种条件组合的场景中使用,排序条件组合只不过是冰山一角。Java标准开发包中有一个java.io.File类,定义了很多文件操作方法。其中,有一个方法叫做listFile,能够根据不同的过滤条件选出符合条件的文件列表。listFile这个方法接受一个FileFilter接口类型的对象作为过滤条件。
这个功能有点象我们在文件管理器用通配符来搜索相应的文件。比如,我们想搜索JPG图片文件,我们就在文件搜索框中输入*.JPG。除此之外,我们还可以根据大小、时间等信息来搜索文件。
对于File类的listFile方法,我们可以定制不同的FileFilter,实现不同的过滤条件,比如文件时间在某个范围内;文件后缀名,文件名符合某种模式; 是目录,还是文件,等等。
我们可以应用上述的解决方法,实现灵活的过滤条件组合——用一个CompositeFilter类任意组合过滤条件,用一个ReverseFilter类作为排除条件来过滤掉那些我们不想要的文件。
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;
}
}

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);
}
}

关于这两个类的组合用法,请读者自己尝试,这里不再赘述。
讲到这里,不知读者有没有注意到一个问题。在本书前面的章节中,我曾经强调过,在学习命令式语言时,任何时候,都不要忘记内存模型的概念。在描述C、C++语言特性的时候,我用了很多篇幅来描述复合结构和对象的内存映射结构。而我在用Java语言描述Design Pattern的时候,却有意无意地忽略了这个问题。这是为什么呢?难道Java语言高端到这种程度,都不需要考虑内存模型了吗?
不,不是这样的。下面我就要来讲述Java对象的内存映射问题。之所以推迟到现在来讲,是因为之前我们还没有足够复杂的对象关系的例子。现在,我们已经接触到足够多的、结构足够复杂的Java对象。我们接触到了包含类、组合类、列表类以及数组结构。可以说,Java对象的大部分结构,我们都已经遇到了。
关于Java对象的内存结构,我们应该注意什么呢?Java对象的内存结构,和C++对象有什么区别呢?
在C++语言中,对象有可能分配在内存堆中,也有可能分配在运行栈中。当对象分配在内存堆中的时候,程序员必须自己负责对象内存的释放。当对象分配在运行栈上时,当前过程执行完毕之后,对象内存就会自动释放。因此,在C++中,对象的内存分配情况是比较复杂的。
在Java语言中,情况就简单得多。所有的对象都是在内存堆中分配的。而且,Java虚拟机负责对象的内存自动回收,程序员根本就不用考虑内存释放回收问题。对于C#、Python、Ruby等支持内存自动回收的语言来说,情况也是如此。
在前面的例子中,我们看到,一个Java对象中可以包含另一个Java对象(一个Java类中包含的成员变量的类型是另一个Java类)。这种包含关系,在内存模型中是如何映射的呢?
Java语言中有一个Object Reference(对象引用)的概念。在Java虚拟机中,所有的对象实例都有一个唯一的虚拟机内部的内存地址。这个内存地址,就是Object Reference。当一个对象包含另一个对象的时候,实际上,只是一个对象内部记录了另一个对象的内存地址(即Object Reference)而已。
我们可以想象一下,在一个布满了格子的巨大的内存架上,有两个对象分别占据了两块内存。其中一个对象的内存格中有一个格子记录另一个对象的内存地址。这就形成了一个表面上的包含关系。实际上,这两个对象完全是分立的,最多只能说是一种引用(Reference)关系。从这个意义上来说,Object Reference这个名词是非常贴切的。
Java对象数组也是如此,Java对象数组是一排连续内存格。每一个格子里面都放着一个内存地址,该内存地址记录着某个Java对象在Java虚拟机中的内存地址。
那么,是否存在真正意义上的内存结构的包含呢?是的,存在。比如,C语言的structure类型,C++的class类型,都能够真实的内存包含结构。但是,在Java中,我们只能做到“引用”。这种“引用”关系很有点像关系数据库里面的数据表之间的关系。所有的数据表都只能依靠关联数据来“引用”另一个表,而无法真正地“嵌套包含”另一个数据表。
在有些资料中,用C语言的指针概念来比拟Java的Object Reference概念。这种比拟不能说错。但是,我是坚决摒弃C语言指针概念的。所以,在本书中,我一律用内存地址这个概念来表述Object Reference。
前面讲述了Visitor、Proxy、Composite等常见的、重要的、基本的设计模式。另外,还有一个极其重要的、极其常见的设计模式,我们还没有讲到。那个设计模式就是Iterator Pattern。
实际上,我们在前面的代码例子中已经接触到Iterator了。我写CompositeComparator的时候,用数组来放置多个Comparator对象。但是,我在写CompositeFilter类的代码时,刻意用了LinkedList这个类。当我们需要遍历List数据结构是,我们就必须用到Iterator。还记得那段CompositeFilter类中的accept方法中的代码吗?
for(Iterator iterator = filters.iterator(); iterator.hasNext();)
上述代码中的Iterator的概念和用法,不仅在Java语言中极为常见,在其他的高级语言中也极为普遍。
注:上述代码中的Iterator用法极为冗长繁琐,在比Java更高级的语言中,在Java语言的高级版本,都针对这种用法进行了简化。这里采用这种冗长写法,是为了更清楚地表示出Iterator接口的具体方法。
Iterator Pattern的重要性,怎么强调也不为过。这也是本书重点讲解的Design Pattern之一。
但是,Iterator Pattern的实现,可不像前面讲述的那些基本Design Pattern那么简单。Iterator Pattern的实现,涉及到复杂的机理和相关知识。在讲述Iterator Pattern之前,我们必须做好相应的知识储备。下一章,我们讲解线程相关的种种概念和模型。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象接口 9 1.6.4 描述对象的实现 10 1.6.5 运用复用机制 13 1.6.6 关联运行时刻和编译时刻的 结构 15 1.6.7 设计应支持变化 16 1.7 怎样选择设计模式 19 1.8 怎样使用设计模式 20 第2章 实例研究:设计一个文档编 辑器 22 2.1 设计问题 23 2.2 文档结构 23 2.2.1 递归组合 24 2.2.2 图元 25 2.2.3 组合模式 27 2.3 格式化 27 2.3.1 封装格式化算法 27 2.3.2 CompositorComposition 27 2.3.3 策略模式 29 2.4 修饰用户界面 29 2.4.1 透明围栏 29 2.4.2 Monoglyph 30 2.4.3 Decorator 模式 32 2.5 支持多种视感标准 32 2.5.1 对象创建的抽象 32 2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge 模式 40 2.7 用户操作 40 2.7.1 封装一个请求 41 2.7.2 Command 类及其子类 41 2.7.3 撤消和重做 42 2.7.4 命令历史记录 42 2.7.5 Command 模式 44 2.8 拼写检查和断字处理 44 2.8.1 访问分散的信息 44 2.8.2 封装访问和遍历 45 2.8.3 Iterator类及其子类 46 2.8.4 Iterator模式 48 2.8.5 遍历和遍历过程中的动作 48 2.8.6 封装分析 48 2.8.7 Visitor 类及其子类 51 2.8.8 Visitor 模式 52 2.9 小结 53 第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 模式 87 3.5 Singleton(单件)—对象创建型 模式 84 3.6 创建型模式的讨论 89 第4章 结构型模式 91 4.1 Adapter(适配器)—类对象结构型 模式 92 4.2 Bridge(桥接)—对象结构型 模式 100 4.3 Composite(组成)—对象结构型 模式 107 4.4 Decorator(装饰)—对象结构型 模式 115 4.5 FACADE(外观)—对象结构型 模式 121 4.6 Flyweight(享元)—对象结构型 模式 128 4.7 Proxy(代理)—对象结构型 模式 137 4.8 结构型模式的讨论 144 4.8.1 Adapter与Bridge 144 4.8.2 Composite、Decorator与Proxy 145 第5章 行为模式 147 5.1 CHAIN OF RESPONSIBIL ITY(职责链) —对象行为型模式 147 5.2 COMMAND(命令)—对象行为型 模式 154 5.3 INTERPRETER(解释器)—类行为型 模式 162 5.4 ITERATOR(迭代器)—对象行为型 模式 171 5.5 MEDIATOR(中介者)—对象行为型 模式 181 5.6 MEMENTO(备忘录)—对象行为型 模式 188 5.7 OBSERVER(观察者)—对象行为型 模式 194 5.8 STATE(状态)—对象行为型模式 201 5.9 STRATEGY(策略)—对象行为型 模式 208 5.10 TEMPLATE METHOD(模板方法) —类行为型模式 214 5.11 VISITOR(访问者)—对象行为型 模式 218 5.12 行为模式的讨论 228 5.12 1 封装变化 228 5.12.2 对象作为参数 228 5.12.3 通信应该被封装还是被分布 229 5.12.4 对发送者和接收者解耦 229 5.12.5 总结 231 第6章 结论 232 6.1 设计模式将带来什么 232 6.2 一套通用的设计词汇 232 6.3 书写文档和学习的辅助手段 232 6.4 现有方法的一种补充 233 6.5 重构的目标 233 6.6 本书简史 234 6.7 模式界 235 6.8 Alexander 的模式语言 235 6.9 软件中的模式 236 6.10 邀请参与 237 6.11 临别感想 237 附录A 词汇表 238 附录B 图示符号指南 241 附录C 基本类 244 参考文献 249
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值