UML软件工程组织

从田忌赛马谈Iterator模式
软件的信雅达

在战国时代的齐国,赛马是一种非常流行的贵族运动。这些贵族们,上至国王,下至大臣,都以赛马为乐。不但如此,还经常以重金赌输赢。他们中间,据说大将田忌和齐王尤好此术。

一天,齐王和田忌照常来赛马。当时的情况是这样的:田忌和齐王的马都分为上、中、下三个等级。其中,齐王的上等马比田忌的上等马快;中等马比田忌的中等马快,而不如田忌的上等马;下等马比田忌的下等马快,而不如田忌的中等马。

我们假设齐王的上等马的速度为6,田忌的上等马的速度为5,齐王的中等马的速度为4,田忌的中等马的速度为3,齐王的下等马的速度为2,田忌的下等马的速度为1。现在我们来用程序模拟田忌赛马。

首先,我们做一个Horse类,来用描述马匹。如下:

public class Horse {
private String dec;
private int speed;

public Horse(String des,int speed)
{
this.des = des;
this.speed = speed;
}

public String getDec() {
return dec;
}

public void setDec(String dec) {
this.dec = dec;
}

public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}

}

下面的类是用来确定单场比赛两匹马的胜负,如下:

public class HorseRace {
private Horse king;
private Horse tian;
public HorseRace(Horse king,Horse tian)
{
this.king = king;
this.tian = tian;
}
public int race()
{
return this.king.getSpeed()>this.tian.getSpeed()?1:-1;
}
}

这个类也比较简单,构造器的第一个参数输入齐王的马匹,后一个参数输入田忌的马匹。方法race()用来确定本次比赛的胜负,返回1,则齐王的马匹胜;返回-1,则田忌的马匹胜。

然后是齐王和田忌的排兵布阵。田忌决定用他的上等马来对付齐王的上等马,用他的中等马来对付齐王的中等马,用他的下等马来对付齐王的下等马。模拟如下:

HorseRace hr1 = new HorseRace(new Horse("King,best",6),new Horse("tian,best",5));
HorseRace hr2= new HorseRace(new Horse("king better",4),new Horse("tian,better",3));
HorseRace hr3 = new HorseRace(new Horse("king good",2),new Horse("tian,good",1));
List list = new ArrayList();
list.add(hr1);
list.add(hr2);
list.add(hr3);

最后是关于正式比赛的模拟,如下:

int sum = 0;
for(int i=0;i<list.size();i++)
{
if(((HorseRace)list.get(i)).race()==1) sum++;
}
if(sum>=2)
{
System.out.println("king win!");
}
else
{
System.out.println("tian win!");
}

最后的比赛结果为:

King win!

田忌输了比赛,心里不痛快。他将我们的模拟程序拿过去看了一眼,顿时大发雷霆:你们这群笨蛋,写的什么代码,难道要我下次比赛还把你们的代码重新抄写一遍,然后再改相应的需要改动的很小一部分吗?我们一听不禁哑然失笑,这家伙输了还想下次赢回来呢?
不过一想也是,下次比赛的规则还一样,可能只是马匹的顺序不一样。也就是变量king和变量tian不一样。所以我们要把模拟比赛的那段代码独立出来。

小结:很明显,我们需要把模拟排兵布阵的那段代码和模拟整个比赛结果的代码分开来。这样我们对模拟整个比赛结果的代码就可以实现重用,不管进行多少次比赛,所不同的只是模拟排兵布阵的那段代码,而模拟整个比赛结果的代码都是相同的。
现在,我们来抽取来模拟整个比赛结果的代码。如下:

public void wholeRace(List raceOrder)
{
int sum = 0;
for(int i=0;i<raceOrder.size();i++)
{
if(((HorseRace)raceOrder.get(i)).race()==1) sum++;
}
if(sum>=2)
{
System.out.println("king win!");
}
else
{
System.out.println("tian win!");
}
}

这样,下次他们再比赛的时候,只要把马匹的比赛顺序安排好,存入一个List类型的变量里;然后调用wholeRace方法就行。

很快,田忌又迫不及待的和齐王举行了第二次比赛。这次他带来了一个帮手,就是他的军师孙膑先生。孙先生来帮他排兵布阵,结果孙先生写下了这样的代码:

HorseRace hr1 = new HorseRace(new Horse("King,best",6),new Horse("tian,good",1));
HorseRace hr2 = new HorseRace(new Horse("king better",4),new Horse("tian,best",5));
HorseRace hr3 = new HorseRace(new Horse("king good",2),new Horse("tian,better",3));
Vector v = new Vector();
v.add(hr1);
v.add(hr2);
v.add(hr3);

然后,孙先生就将这个排法给了我们,让我们来模拟比赛结果。我们一看就傻眼了,只见孙先生给我们一个Vector类型的对象,这显然不能作为参数来调用wholeRace方法。于是我们就去请求孙先生把变量类型改过来,没想到他神秘一笑,道,“该你们改才是”。我们无可奈何,只得回来改我们的wholeRace方法。

怎么办?我们不能把变量raceOrder的类型改为Vector类型,天才晓得下一次孙先生会不会又给我们一个List类型的变量,这样我们就顾此失彼。实在没有办法,最后,我们想到了Iterator模式,将代码修改如下:

public void wholeRace(Iterator raceOrder)
{
int sum = 0;
while(raceOrder.hasNext())
{
if(((HorseRace) raceOrder.next()).race()==1) sum++;
}
if(sum>=2)
{
System.out.println("king win!");
}
else
{
System.out.println("tian win!");
}
}

这下我们放心了,不管下次孙先生是用List也好,还是Vector也罢,都可以取得Iterator类型的对象,然后就可以使用我们的wholeRace方法。
下面,我们来模拟整个比赛的结果,如下:

wholeRace(v.iterator());

运行结果为:

tian win!

田忌将军赢了,孙先生真乃世外高人也。但这是题外之话,这里不便多说。我们乃作技术之人,当然要更关注技术本身才是。

小结:所谓Iterator模式,就是将算法和容器区别开来,在算法里是用算子进行遍历。这样,在算法不变的情况下,只要容器能够实现算子运算,都可以调用我们的算法。

我们再回头来看看我们的wholeRace(Iterator raceOrder),这个方法正是使用了Iterator模式,使得我们不用再考虑每次比赛排兵布阵后给出的参数,这个参数是一个容器类,不管是哪个容器类,没关系,只要该容器能够实现Iterator算子,我们都可以使用我们的这个算法。

一句话,Iterator模式是用来满足依赖颠倒原则的。我们的算法被分离开来以后,最后让它依赖所有容器类的抽象。而Iterator类正是这样的抽象。

话说,齐王输了第二次比赛,正是百思不得其解:怎么说哪一个等级的马寡人的都明显好过田忌的马,怎么会输给他呢?于是齐王急急的要跟田忌比第三场;而田忌呢?有了孙膑先生助阵,他现在一点都不怕,毫不示弱地答应了。

第三场比赛,齐王宣布了新的比赛规则:齐王和田忌分别拿出六匹马来比赛,这六匹马按速度来表示,齐王的为:12,10,8,6,4,2;而田忌的为:11,9,7,5,3,1。这场比赛赛六场,但是只要其中的2,4,6场决定胜负,而这三场中胜两场的赢得最后的胜利。

很快,孙先生又给了我们一个排兵布阵的模拟代码,如下:

HorseRace hr1 = new HorseRace(new Horse("king one",2),new Horse("tian one",3));
HorseRace hr2 = new HorseRace(new Horse("king two",12),new Horse("tian two",1));
HorseRace hr3 = new HorseRace(new Horse("king three",4),new Horse("tian three",5));
HorseRace hr4 = new HorseRace(new Horse("king four",10),new Horse("tian four",11));
HorseRace hr5 = new HorseRace(new Horse("king five",6),new Horse("tian five",7));
HorseRace hr6 = new HorseRace(new Horse("king six",8),new Horse("tian six",9));
List list = new ArrayList();
list.add(hr1);
list.add(hr2);
list.add(hr3);
list.add(hr4);
list.add(hr5);
list.add(hr6);

我们一看代码,容器对象里有六个元素,而我们只需要三个。于是,我们小声问孙先生,直接给出2,4,6局的排法不就行了,为什么要把六局的排法全部放进去?孙先生道,那几局你们不关心,还有人关心啊?有很多观众在赌六局四胜呢。我不把这六局的排法全部给出来,假如有人问你要六局四胜的结果,你们怎么办?

我们一想也是。不过没关系,这点问题吓不到我们。既然我们不能直接使用list对象的iterator方法,那么我们来实现我们自己的Iterator对象。首先,做一个我们自己的Iterator类,如下:

public class MyIterator implements Iterator{
private Iterator it;
public MyIterator(Iterator it)
{
this.it = it;
}
public void remove()
{

}
public boolean hasNext()
{
return this.it.hasNext();
}
public Object next()
{
this.it.next();
return this.it.next();
}
}

这样,我们就可以使用如下的方法来调用wholeRace方法了:

wholeRace(new MyIterator(list.iterator()));

运行结果为:

tian win!

需要注意的是:我们的MyIterator并不是对Iterator接口的完全实现,如上面的代码所示,它没有实现remove方法。这是值得我们注意的。

小结:使用Iterator模式,不但能够对JDK中已经实现了的容器类的依赖,而且能够对我们自己的容器类进行依赖,只要我们自己实现的容器类实现了iterator方法。

当现有的容器不够用时,我们需要实现我们自己的容器类,这时候,对我们已经存在了的依赖Iterator接口的算法,仍然是不足为虑的,因为只要这个新的容器类实现新的iterator方法既可。如上面的解决方案。


版权所有:UML软件工程组织