设计模式初涉
对 << Think In Java >> 中的设计范式示例的摘录和理解
模拟垃圾回收站
原始模式
我们的任务很简单,就是把不同类别的垃圾分类,不细加思考的话,很快就能设计出类似于下面的模式,我把它叫做原始模式
// 收集
switch((int)(Math.random() * 3)) {
case 0 :
bin.addElement(new
Aluminum(Math.random() * 100));
break;
case 1 :
bin.addElement(new
Paper(Math.random() * 100));
break;
case 2 :
bin.addElement(new
Glass(Math.random() * 100));
}
Vector glassBin = new Vector(), paperBin = new Vector(), alBin = new Vector();
Enumeration sorter = bin.elements();
// 分类
while(sorter.hasMoreElements()) {
Object t = sorter.nextElement();
// RTTI to show class membership:
if(t instanceof Aluminum)
alBin.addElement(t);
if(t instanceof Paper)
paperBin.addElement(t);
if(t instanceof Glass)
glassBin.addElement(t);
}
这一段是书上的旧版本java代码,switch块模拟随机产生垃圾
垃圾的结构:
image设计结构我理解为这样:
image如果只是应付课程设计,应该足够拿到学分~~。但观察一下,从图中很容易发现两个问题
- 收集过程Trash子类被上溯造型以使容器容纳,分类过程又回过头重新下溯到原来的类型,这样似乎很笨拙
- 设想需要增加多种Trash,程序立马显得难以应付。起码我又得在图上增加无数的箭头
创建范式
书中这样介绍:
Factory 方法的基本原理是我们将创建对象所需的基本信息传递给它,然后返回并等候句柄(已经上溯造型
至基础类型)作为返回值出现。从这时开始,就可以按多形性的方式对待对象了。因此,我们根本没必要知
道所创建对象的准确类型是什么
简单的增加一个Info对象,以承载基本信息
class Info {
int type;
// Must change this to add another type:
static final int MAX_NUM = 4;
double data;
Info(int typeNum, double dat) {
type = typeNum % MAX_NUM;
data = dat;
}}
把烦人的switch语句放到factory里,让它根据不同的参数返回不同的对象。创建的代码变成这样
for(int i = 0; i < 30; i++)
bin.addElement(
Trash.factory(
new Info(
(int)(Math.random() * Info.MAX_NUM),
Math.random() * 100)));
现在的结构看上去是这样。
imageTrash的创建由Factory控制,主程序不知道任何创建的细节。尽管从代码上看简化并不大,但现在创建过程关注点集中在Factory,不管有没有对象新增,只要它能够返回可用的对象,则对主程序不会有影响。
总之Factory将变化的影响隔离出来了,比前一个设计更简单、清爽
原型范式
static Trash factory(Info i) {
switch(i.type) {
default: // To quiet the compiler
case 0:
return new Aluminum(i.data);
case 1:
return new Paper(i.data);
case 2:
return new Glass(i.data);
// Two lines here:
case 3:
return new Cardboard(i.data);
}
}
第一眼看上去,Factory不过是把switch语句放到了一个函数里,事实上也确实是这样。除了提到的好处之外,Factory本身还是逃不过笨拙的创建过程。试试再深入一步:
将与类型有关的所有信息——包括它的创建过程——都移入代表那种类型的类内部
public static Trash produce(Info info) {
for (Class<?> type : trashTypes){
if (type.getName().contains(
info.typeName
)){
try {
Constructor<?> ctor = type.getConstructor(
double.class
);
return (Trash)ctor.newInstance(
new Object[]{info.weight}
) ;
} catch (InstantiationException |IllegalAccessException
|InvocationTargetException |NoSuchMethodException e) {
e.printStackTrace();
return null ;
}
}
}
// new trash type
System.out.println("Loading trash type: " + info.typeName);
try {
trashTypes.add(
Class.forName(info.typeName)
);
} catch (ClassNotFoundException e){
// Unknown trash type.
// Sort it to OTHER
return new OtherTrash(info.weight);
}
return produce(info) ;
}
这段代码可能有些迷糊,基本想法是根据类名(info.typeName),保存Class句柄,然后使用Java的反射机制产生需要的对象。如果想查阅完整的代码可以去书上看。
总之一点,这里没有了 switch 语句,factory会根据类名自动产生需要的对象。trashTypes容器起到的正是原型的作用,就跟工厂里的一系列模具一样,在模具上加工就能生产出各种各样的产品,模具和加工正对应这个例子中的输入
com.chapter.sixteen.trash_collector.Glass:54
现在结构图是这样:
image抽象的应用
创建的过程已经做得很好了,但讨厌的 if 语句还是一直伴随着我们
如果必须做不雅的事情,至少应将其本地化到一个类里
再深入思考,可以得到这样的设计
image再次,将分类的细节隐藏到bins里,从实现上要解决一个问题
用静态方式编码的方法如何应付一种新类型加入的事实
也就是说bin要从1.不同的trash中收集2.对应的trash。解决前者很容易,java的继承已经做到了。后者则又需要我们把Class对象拿出来使用了,即RTTI(运行时类型鉴定)。具体是为每一个bin保存一个class对象,在分类时遍历bins里面的bin,放入对应的bin里面:
尾声
那么到最后,程序的结构是这样
image只需要在init中增添一个配置,就可以使produce产生不同的结果(装着不同垃圾的垃圾桶:) )其他的行为都不会受到影响
此中有真意,欲辨已忘言