开宗明义 话说设计模式以来,众人皆奉为经典,其实说穿了不过是一个矛盾的辩证关系而已,即如 何将变化与固定相统一的问题。通常的代码是固定的,很难变化; 而需求与环境是变化的,故而两者是矛盾的。但我们却要在程序中将两者的矛盾处理好。 然而,两者又是统一的,因为变化就是为了“不变”,即最大限度地重用已有的“不变” 代码;另一方面,将“不变”认识清楚,就知道哪些该“变化”,哪些该让它“变化”。 只有在固定的基础上,将该变化的让它变化,程序才能既牢固,又有弹性,从而能够满足 各方面的要求。后面的分析过程,希望大家能够牢记这个要点,从这个角度来看设计模式 ,而不是为学模式而学模式,那样我认为永远都没有自己的领悟。而且设计模式也不是固 定的,随着时间的推移会出现其它的新模式,但我想仍然是符合”变化与固定“这个原则 的。 好了,罗嗦了这么多,我们首先从常见的《创建型模式》说起,我首先不给出干巴巴的定 义,而是给出活生生的例子,从实践出总结出规律,再给出定义就是水到渠成的事情了。
迷宫是许多游戏的组成元素,玩家在迷宫时探索,从一个入口进入迷宫,在迷宫中穿行, 与遇到的敌人激战,捡取医药包、武器等物品,最后找到出口,其间经常从一个房间进入 另一个房间,碰了壁就走过其它的路,如此反复。姑且不考虑这么复杂,我们现在仅关心
迷宫的创建。遵循着“将问题简化到不能简化”的原则,我们假设此迷宫中仅有两个房间 和一扇门,从其中一个房间推开门就可以进入另一个房间。整个迷宫的坐标是二维的,即 使用“东南西北”作为方位。 我们首先大致写出迷宫中房间、门、墙的框架,再接着我们的讨论(我们使用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有许多的接口,可以完成创建组成对象的工作,我们称之为“工厂”,工厂中创 建对象的方法,我们称之为“工厂方法”。 通过在具体的工厂中,重写工厂方法,或者说生成具体的工厂方法,生产出具体的产品, 达到所需的效果,这应是“工厂方法”的几个要素。
|
|