Java 版 (精华区)

发信人: strongstorm (强力风暴), 信区: Java
标  题: java设计模式
发信站: 哈工大紫丁香 (2002年11月07日10:02:52 星期四), 站内信件

标  题: 设计模式入门之一
发信站: BBS 水木清华站 (Tue Nov  5 19:17:53 2002)

开宗明义
话说设计模式以来,众人皆奉为经典,其实说穿了不过是一个矛盾的辩证关系而已,即如
何将变化与固定相统一的问题。通常的代码是固定的,很难变化;
而需求与环境是变化的,故而两者是矛盾的。但我们却要在程序中将两者的矛盾处理好。
然而,两者又是统一的,因为变化就是为了“不变”,即最大限度地重用已有的“不变”
代码;另一方面,将“不变”认识清楚,就知道哪些该“变化”,哪些该让它“变化”。
只有在固定的基础上,将该变化的让它变化,程序才能既牢固,又有弹性,从而能够满足
各方面的要求。后面的分析过程,希望大家能够牢记这个要点,从这个角度来看设计模式
,而不是为学模式而学模式,那样我认为永远都没有自己的领悟。而且设计模式也不是固
定的,随着时间的推移会出现其它的新模式,但我想仍然是符合”变化与固定“这个原则
的。
好了,罗嗦了这么多,我们首先从常见的《创建型模式》说起,我首先不给出干巴巴的定
义,而是给出活生生的例子,从实践出总结出规律,再给出定义就是水到渠成的事情了。


迷宫是许多游戏的组成元素,玩家在迷宫时探索,从一个入口进入迷宫,在迷宫中穿行,
与遇到的敌人激战,捡取医药包、武器等物品,最后找到出口,其间经常从一个房间进入
另一个房间,碰了壁就走过其它的路,如此反复。姑且不考虑这么复杂,我们现在仅关心
迷宫的创建。遵循着“将问题简化到不能简化”的原则,我们假设此迷宫中仅有两个房间
和一扇门,从其中一个房间推开门就可以进入另一个房间。整个迷宫的坐标是二维的,即
使用“东南西北”作为方位。
我们首先大致写出迷宫中房间、门、墙的框架,再接着我们的讨论(我们使用MapSite作为
所有组件的协议接口):
interface MapSite {
    public void enter();
}

class Room implements MapSite {
    public Room(int roomNo) {
        this.roomNo = roomNo;
    }

    public void enter() {
    }

    public MapSite getSide(int n) {
        return _sides[n];
    }

    public void setSide(int n,MapSite ms) {
        _sides[n] = ms;
    }
}

class Wall implements MapSite {
    public Wall() {
    }

    public void enter() {
    }
}

class Door implements MapSite {
    public Door(Room r1,Room r2) {
        _room1 = r1;
        _room2 = r2;
    }

    public void ener() {
    }
}

//迷宫类
class Maze {
    public Maze() {
    }

    public void addRoom(Room r) {
    }

    public Room roomNo(int) {
    }
}

有了砖瓦,就可以盖房子了。我们利用这些原料来搭建一个迷宫。我们能想到的最自然、
最简单的方法,就是在迷宫游戏类的一个方法中写下创建代码:
class MazeGame {
    public Maze createMaze() {
        Maze aMaze = new Maze();
        Room r1 = new Room(1);
        Room r2 = new Room(2);
        Door theDoor = new Door(r1,r2);

        aMaze.addRoom(r1);
        aMaze.addRoom(r2);

        r1.setSide(North,new Wall());
        r1.setSide(East,theDoor);
        r1.setSide(South,new Wall());
        r1.setSide(West,new Wall());

        r2.setSide(North,new Wall());
        r2.setSide(East,new Wall());
        r2.setSide(South,new Wall());
        r2.setSide(West,theDoor);

        return aMaze;
    }
}
在createMaze的代码中,我们写下了迷宫构成部件的生成代码和迷宫本身的布局代码。那
么,我们从“变与不变的角度”可以知道,整个代码部分都是可以变化的.
大家好好想一想,一个游戏可能会升级,今天的一个房间里可能是一个医药包,明天可能
你得要钥匙才能打开这个门取得这个医药包,后来还有可能是其它什么东东。
这么多哇?!别急,我们还知道,今天,这个迷宫的地图是这样,明天可能是那样,后天
。。。打住,读者可能头都大了,你改来改去我的代码不是成马蜂窝了吗?
我的代码是死的,怎么可能一会儿是这个,一会儿是那个,不是要我命吗?嘿嘿,别急,
我们要承认这样一个事实。[需求是不断改变的],正如马原所讲[运动是绝对的,静止是相
对的],只有正视这个问题,我们才能开发出合乎要求的软件来。我决定对这个问题按两个
方面来讨论,首先解决迷宫中的门变化的问题,后面的文章再讨论迷宫的布局变化的问题
,必竟,饭要一口一口吃吗!

请大家思考一下,上面的代码要怎样才能满足变化的需要呢?首先,我们承认,代码只有
变化,才能反映要求的变化,只是如何变化才巧妙而已。在程序代码中,虚方法是变化的
不二法门。为什么这么说呢?
因为虚方法可以让你在“不变”的基础上达到“变化”的目的。不是吗?所有的OO教材上
都把这一点作为OO的头一优点来大书特书,我们也来这样操作一番。可是,我要变化的创
建房子、墙、门,这些不可能改变呀,因为初始化的语法就限制了你必须使用构造方法名
,而构造方法名与类名是相同的,你要改变成另一个类,就必须在迷宫创建代码中将名称
改变成新类的名称。
OK,我们找到了问题的所在,我们要改变创建的类,而上面的代码不让我们改变,这是语
言限制,我们只要找一种既能创建自己的对象又能无需改变创建代码的方法。没关系,我
在这个迷宫代码中非要使用构造器来创建一个类的对象吗?
不用,虽然本质上我们最终还是要使用创建语法,但在上面的代码中,我们完全不用这么
做?我们代之以虚方法?
Room createRoom(int roomNo) {
    return new Room(roomNo);
}
代入上面的代码后,变为
Room r1 = createRoom(1);
Room r2 = createRoom(2);
如果再有新的房子出来,只要提供另一个createRoom的实现,上面的代码只要布局不变,
是不用改变的,不是吗?OK!
只是,在OO中,所有的方法都应该是成员方法或类方法,上面的方法是哪个类的呢?显然
,我们可以将createRoom作为MazeGame的成员方法。于是,我们又有了createWall和crea
teDoor方法,要改变创建的东西,我们只要继承MazeGame,
重写createXXX方法,不就达到了“不变”的目的吗?下面是新的MazeGame的代码:
class MazeGame {
    public MazeGame() {
    }

    public Room createRoom(int roomNo) {
        return new Room(roomNo);
    }

    public Wall createWall() {
        return new Wall();
    }

    public Door createDoor(Room r1,Room r2) {
        return new Door(r1,r2);
    }

    public Maze createMaze() {
        //ommit here
    }
}

到此为止,大家学会了一种简单实用的设计模式,即Factory Method,希望大家再接再厉
,跟我一起来了解一下Factory Method的理论,毕竟实践清楚了,就要上升到理论高度,
同意吗?
冷静地分析一下,问题的关键在MazeGame类,此类预留了若干个接口,让子类通过重写这
些接口,来达到实例化“具体的类”,这些“具体的类”用来构成Maze类。
这个框架能够改变类的实体,但无法改变类之间的组合关系(这应是本方法的缺点)。再
来看,我们是将具体的创建Room、Wall、Door的操作抽象成对应的方法,使得创建与其他
代码隔离,达到重用这些其他代码的目的。这也是“创建型模式”的名称由来。

MazeGame有许多的接口,可以完成创建组成对象的工作,我们称之为“工厂”,工厂中创
建对象的方法,我们称之为“工厂方法”。
通过在具体的工厂中,重写工厂方法,或者说生成具体的工厂方法,生产出具体的产品,
达到所需的效果,这应是“工厂方法”的几个要素。

总结:这里的工厂方法,我们是不加其他思考的赋予在工厂对象上,如果将这部分对象分
离出来,成为一个粒度更小的,与MazeGame分离的类来实现,就成了为 Abstract Factor
y模式。

请大家继续关注后面章节,并提出宝贵意见。必竟我也是抱着学习的目的写下这个系列的
。有错误的指出错误,有不足的指出不足。欢迎来电来函,请播"888-88888" :(

VirtualMFC 2002-11-5

--
世上本没有路,走的人多了,自然就成了路。
走自己的路,让别人说去。
--


--
    一些花开了又谢了,一些鸟来了又走了,一些手牵了又断了,一些泪流了又干了, 
一些人爱了又不爱了,一些人孤单过仍孤单着! 

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.136]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:2.550毫秒