Joe所在的电脑游戏公司研发了一款游戏软件,模拟池塘中各种鸭子嬉戏(游动并且鸣叫)的场景。软件很受欢迎。
设计一个Duck超 类,各种鸭子都继承这个超类。所有的鸭子都会鸣叫和游水,所以Duck超类实现quack()和swim()方法。由于不同种类的鸭子样子不同,所以 Duck中的display()方法设计为抽象方法。不同的鸭子子类通过实现display方法显示自己的样子。
游戏上市后的次年,竞争越来越激烈。在外出度假一周后,公司高管们决定要对游戏作重大创新,在下周的股东大会上,他们要让股东们“印象深刻”。
高管们觉得,如果鸭子能够飞起来,则能击败同类游戏的竞争者。Joe的上司告诉高管们,Joe可以在一周内搞定,因为他是一个 OO Programmer…
但 是,一周后Joe的经理从股东大会上打来电话…Joe,我在股东大会上,刚刚演示了游戏,他们看到屏幕上有许多橡皮玩具鸭子在飞来飞去,你是不是故意搞 笑?!偶的错,偶忘了不是所有的鸭子都会飞!在Duck中增加一个fly()方法,则它的所有子类就都有飞的能力了。没想到,一个局部的修改产生了非局部 的副作用。
在子类RubberDuck中重写fly()方法,让它什么都不做。还有,橡皮鸭也不会嘎嘎叫,只会吱吱叫,这样quack()方法也要改写。但是,如果考虑再增加打猎用的仿真鸭该怎么办呢?它既不会叫也不会飞,这样又得改写方法了。烦!看来,继承这东西有时也挺烦人的!
公司高层决定每半年就要更新该产品,更新的方式他们还没有想好。。。。。。。。。。。。。。。
必须要有一种清晰的方法,让一部分鸭子(而不是全部)能够飞行和鸣叫。把fly()从Duck中分离出来,设计一个接口Flyable,该接口有一个fly()方法,会飞的鸭子就实现该接口。对quack()也依此处理。
Joe的第二个方案解决了部分问题(不会有会飞的橡皮鸭了),但也完全破坏了代码的重用性(每一种会飞的鸭子都必须实现fly()方法,如果要修改飞行行为,则必须修改每种鸭子的fly()代码,设想有几十种会飞的鸭子)。这从另一个方面增加了维护的难度。
正确的解决方案
现在让我们将鸭子的行为从Duck类中分离出来!
我们已经知道,fly()和quack()是Duck类中不同的ducks的变化部分。我们将这些行为从Duck类中分离出来,对于每一个方法,创建一个类的集合来表示相应的行为。例如:对于fly(),我们建立一个类的集合,分别实现翱翔,俯冲,起飞等。
怎么设计实现鸭子飞行和鸣叫行为的类集合?
首 先,我们需要灵活性。在此之前,正是由于不灵活给我们带来了麻烦。我们希望能够将行为指定给鸭子的某个实例。例如,我们可能希望生成一个野鸭实例,并以某 种飞行行为(例如:翱翔)对它初始化,在这之后,我们就会想能不能动态的改变这只野鸭的行为呢?换句话说,我们应该在Duck类中包含一个设置行为的方 法,使得我们能够在运行时改变野鸭的行为(如:由翱翔变为俯冲)
引入接口
对每类行为,我们用一个接口(interface)来表示。例如:用接口FlyBehavior来表示飞行行为,用接口QuackBehavior来表示鸣叫行为。每一个具体的行为类,是这些接口的一个实现(如:翱翔Hover,俯冲Dive,不飞)。
这样做之后,就不是由Duck类来实现飞行和鸣叫接口了,取而代之的是,我们设置了一组类,他们的任务就是表示某个行为,这些行为类将用来实现上面的接口。
新设计方法与以前的方法的差异
这 种做法与我们以前的做法不同,以前我们或者是用Duck超类,或者是用Duck的子类本身来具体实现(implements)一个接口。后两种做法都使我 们依赖于一个实现,我们被使用这种特定的实现捆住了手脚,要改变鸭子的行为时除了对Duck类或其子类进行修改,增加代码外,别无它法。
在我们新的设计方案中,Duck的子类将使用由接口(FlyBehavior或QuackBehavior)表示的行为,而接口由行为类具体实现,这样,行为的具体实现就不会被固化在Duck的子类中了。
代码列表
1.Duck.java
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public void setFlyBehavior (FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
} 2.FlyBehavior.java
public interface FlyBehavior {
public void fly();
}
3.QuackBehavior.java
public interface QuackBehavior {
public void quack();
} 4.FlyWithWings.java
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
5.FlyNoWay.java
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
6.FlyRocketPowered.java
public class FlyRocketPowered implements FlyBehavior {
public void fly() {
System.out.println("I'm flying with a rocket");
}
}
7.MuteQuack.java
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("<< Silence >>");
}
} 8.FakeQuack.java
public class FakeQuack implements QuackBehavior {
public void quack() {
System.out.println("Qwak");
}
} 9.MallardDuck.java
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
} 10.ModelDuck.java
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
public void display() {
System.out.println("I'm a model duck");
}
} 11.MiniDuckSimulator.java
public class MiniDuckSimulator{
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
The End--------------------!