设计模式初涉

2017-12-06  本文已影响0人  NickYadance

对 << 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

如果只是应付课程设计,应该足够拿到学分~~。但观察一下,从图中很容易发现两个问题

  1. 收集过程Trash子类被上溯造型以使容器容纳,分类过程又回过头重新下溯到原来的类型,这样似乎很笨拙
  2. 设想需要增加多种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)));

现在的结构看上去是这样。

image

Trash的创建由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

尾声

那么到最后,程序的结构是这样

image

只需要在init中增添一个配置,就可以使produce产生不同的结果(装着不同垃圾的垃圾桶:) )其他的行为都不会受到影响

此中有真意,欲辨已忘言

上一篇下一篇

猜你喜欢

热点阅读