问题描述:模拟鸭子游戏
鸭子分为绿头鸭子、红头鸭子、橡皮鸭子、诱饵鸭子,鸭子有的会游泳,有的会飞、有的会呱呱叫、有的会吱吱叫、外表不一样。
问题解决:
问题中所有的鸭子都会叫quack也会游泳swing,通过超类负责处理这部分代买,每种鸭子的外表都不一样,所以display()方法是抽象方法。
现在我们让鸭子飞起来:
我们在duck类中加上fly()方法,然后所有的鸭子都会继承fly()。
通过这样的设计,发生了可怕的问题:
并不是所有的鸭子都会飞(如橡皮鸭),在超类中加上fly(),就导致了所有的子类都具备fly(),连那些不具备fly()的子类也无法免除,说明了继承并不是一个实现的很好方法。
当然我们可以覆盖父类中的方法,如橡皮鸭类RubberDuck中的fly()方法覆盖掉(什么都不做);诱饵鸭类DecoyDuck要不具有其他的行为,只需要disply()方法。
继承不可以我们可以考虑使用接口吗?
从分析来看,使用继承来实现鸭子模拟游戏,当然也可以实现,但是这将会是对方法的多次覆盖,如fly()、quack().........................对一个程序员来来说,简直是一个无穷尽的恶梦!!!
然而,我们可以考虑使用接口,将fly()、quack()等存在变化的方法取出来。fly()放进一个Flyable接口中,只有会飞的鸭子才会实现此接口,同样的方式qucak()等方法也一样。从而得到如下的设计:
很明显,重复的代码变得更多了。使用接口解决了一部分问题(如不会有会飞的橡皮鸭),但是无法达到代码的复用,达不到设计的目的。只能再重新设计。。。。。。
问题解决的思想:
设计原则:找出可能需要变化的地方,把他们独立出出来,不要和那些不需要变化的代码混在一起。把会变化的部分取出来并封装好,让其他部分不会受到影响。这样一来代码变化之后,出其不意的部分变得很少,系统变得更加有弹性。
设计鸭子的行为:
设计原则:针对接口编程,而不是针对实现编程。
也就是说,鸭子的行为将被放在分开的类中,此类专门提供某行为的实现。这样,鸭子就不需要知道行为的实现细节。
我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都必须实现这些接口之一。鸭子类Duck就不会负责实现Fly和Quack接口,反而是由其他类专门实现FlyBehavior和QuackBehavior,也就是行为类实现行为接口,二不是由Duck类实现行为接口。
部分实现代码:
- FlyBehavior接口:
public interface FlyBehavior {
public void fly();
}
- FlyWithWings行为类实现会飞的行为:
package son;
import bb.FlyBehavior;
//会飞的行为
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I can fly");
}
}
- FlyNoWay行为类实现不会飞的行为:
package son;
import bb.FlyBehavior;
//不会飞的行为
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
- QuackBehavior接口:
package bb;
public interface QuackBehavior {
public void quack();
}
- Quack行为类,实现呱呱叫行为:
package son;
import bb.QuackBehavior;
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("我能呱呱叫!!");
}
}
- Squack行为类,实现吱吱叫行为:
package son;
import bb.QuackBehavior;
public class Squeak implements QuackBehavior{
public void quack() {
System.out.println("我能吱吱叫!!");
}
}
- MuteQuack行为类,不会叫的行为:
package son;
import bb.QuackBehavior;
public class Squeak implements QuackBehavior{
public void quack() {
System.out.println("我不能叫!!");
}
}
通过这样的设计,可以让鸭子的动作被其他对象复用,因为这些对象已经和鸭子类无关了。
整合鸭子的行为:
首先在鸭子类中加入两个实例变量flyBehavior和quackBehavior,声明为借口类型。
public FlyBehavior flyBehavior; //飞的行为
public QuackBehavior quackBehavior;//叫的行为
实现fly()和quack()应有的行为。不亲自处理,委托给借口对象来实现。
public void performQuack() {//叫的行为
quackBehavior.quack();
}
public void performFly() {//飞的行为
flyBehavior.fly();
}
整合MallardDuck类(其他鸭子类也是使用同样的方法):
package son;
import bb.Duck;
//绿头鸭---叫、飞、外表
public class MallarDuck extends Duck{
public MallarDuck() {//构造函数,对鸭子的行为进行处理
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
public void display() {
System.out.println("外观是绿头的");
}
}
代码测试:
编写测试类并运行:
package Main;
import bb.Duck;
public class Main {
public static void main(String arg[]) {
Duck Mallar = new MallarDuck();
Mallar.performQuack();
Mallar.performFly();
}
}