设计一个名为SimUDuck的鸭子模拟游戏,游戏中会出现各种鸭子,有红头鸭,野鸭,橡皮鸭,分析这些鸭子都会叫,会游泳。
此游戏内部使用标准OO技术,设计一个鸭子超类,并让各种鸭子继承此超类。
简单的实现为:
package com.lissdy;
public class SimUDuck {
public static void main(String[] args)
{
MallardDuck duck1=new MallardDuck();
RedheadDuck duck2=new RedheadDuck();
System.out.println("野鸭子出场-----------------");
System.out.println(duck1.quack());
System.out.println(duck1.swim());
System.out.println(duck1.display());
System.out.println("红鸭子出场-----------------");
System.out.println(duck2.quack());
System.out.println(duck2.swim());
System.out.println(duck2.display());
}
}
abstract class Duck
{
public String quack()
{
return "呱呱叫";
}
public String swim()
{
return "会游泳";
}
public abstract String display();
}
class MallardDuck extends Duck
{
public String display()
{
return "外观是绿头";
}
}
class RedheadDuck extends Duck
{
public String display()
{
return "外观是红头";
}
}
现在程序增加需求,需要加入会飞的鸭子。而如果在超类中加上fly()就会导致所有鸭子都具备fly(),连同玩具橡皮鸭也无法免除。
如果继续使用继承的话需要将超类中不符合的行为覆盖,例如:
package com.lissdy;
public class SimUDuck {
public static void main(String[] args)
{
MallardDuck duck1=new MallardDuck();
RedheadDuck duck2=new RedheadDuck();
RubberDuck duck3=new RubberDuck();
System.out.println("野鸭子出场-----------------");
System.out.println(duck1.quack());
System.out.println(duck1.swim());
System.out.println(duck1.display()+" "+duck1.fly());
System.out.println("红鸭子出场-----------------");
System.out.println(duck2.quack());
System.out.println(duck2.swim());
System.out.println(duck2.display()+" "+duck2.fly());
System.out.println("橡皮鸭出场-----------------");
System.out.println(duck3.quack());
System.out.println(duck3.swim());
System.out.println(duck3.display()+" "+duck3.fly());
}
}
abstract class Duck
{
public String quack()
{
return "呱呱叫";
}
public String swim()
{
return "会游泳";
}
public String fly()
{
return "会飞";
}
public abstract String display();
}
class MallardDuck extends Duck
{
public String display()
{
return "外观是绿头";
}
}
class RedheadDuck extends Duck
{
public String display()
{
return "外观是红头";
}
}
class RubberDuck extends Duck
{
public String quack()
{
return "吱吱叫";
}
public String fly()
{
return "不会飞";
}
public String display()
{
return "橡皮鸭";
}
}
使用继承的缺点在于每当有新的鸭子子类出现时,就要被迫检查可能需要覆盖的fly()和quark()。
试着利用接口来解决问题,把fly()从超类中取出来,放进一个“Flyable接口”中,这么一来,只有会飞的鸭子实现此接口。同样的方式,也可以设计一个“Quackable接口”,因为不是所有的鸭子都会叫。
Flyable接口:
package com.lissdy;
public interface Flyable {
String fly();
}
Quackable接口:
package com.lissdy;
public interface Quackable {
String quack();
}
代码实现:
package com.lissdy;
public class SimUDuck{
public static void main(String[] args)
{
MallardDuck duck1=new MallardDuck();
RedheadDuck duck2=new RedheadDuck();
RubberDuck duck3=new RubberDuck();
DecoyDuck duck4=new DecoyDuck();
System.out.println("野鸭子出场-----------------");
System.out.println(duck1.quack());
System.out.println(duck1.swim());
System.out.println(duck1.display()+" "+duck1.fly());
System.out.println("红鸭子出场-----------------");
System.out.println(duck2.quack());
System.out.println(duck2.swim());
System.out.println(duck2.display()+" "+duck2.fly());
System.out.println("橡皮鸭出场-----------------");
System.out.println(duck3.quack());
System.out.println(duck3.swim());
System.out.println(duck3.display());
System.out.println("木头鸭出场-----------------");
System.out.println(duck4.swim());
System.out.println(duck4.display());
}
}
abstract class Duck
{
public String swim()
{
return "会游泳";
}
public abstract String display();
}
class MallardDuck extends Duck implements Quackable,Flyable
{
public String display()
{
return "外观是绿头";
}
public String quack()
{
return "呱呱叫";
}
public String fly()
{
return "会飞";
}
}
class RedheadDuck extends Duck implements Quackable,Flyable
{
public String display()
{
return "外观是红头";
}
public String quack()
{
return "呱呱叫";
}
public String fly()
{
return "会飞";
}
}
class RubberDuck extends Duck implements Quackable
{
public String quack()
{
return "吱吱叫";
}
public String display()
{
return "橡皮鸭";
}
}
class DecoyDuck extends Duck
{
public String display()
{
return "木头鸭";
}
}
执行结果:
并非所有的鸭子都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。虽然接口Quackable和Flyable可以解决一部分问题,但是却造成了代码无法复用。
幸运的是,有一种设计原则,恰好适用于此情况。
设计原则:
找出应用中可能变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
以上设计原则的本质是分开变化和不会变化的部分。
在Duck类中fly()和quack()会随着鸭子的不同而改变,为了把这两个行为从Duck类中分开,需要把它们从Duck类中取出来,建立一组新类来代表每个行为。
这时遵循第二个设计原则
设计原则:
针对接口编程,而不是针对实现编程。
将鸭子的fly()和quack()行为设计成FlyBehavior接口和QuackBahavior接口,制造一组“行为类”专门实现FlyBehavior接口和QuackBahavior接口。
整合鸭子的行为:
1、在Duck类中加入两个实例变量,分别为FlyBehavior接口和QuackBahavior接口。
2、用两个相似的方法performQuack()和performFly()取代Duck类中的fly()和quack()
FlyBehavior接口和QuackBahavior接口:
public interface QuackBehavior {
String quack();
}
public interface FlyBehavior {
String fly();
}
改造后的Duck类
abstract class Duck
{
QuackBehavior quackbehavior;
FlyBehavior flybehavior;
public String swim()
{
return "会游泳";
}
public abstract String display();
public void performFly()
{
flybehavior.fly();
}
public void performQuack()
{
quackbehavior.quack();
}
}
3、用“行为”类实现接口
class Quack implements QuackBehavior
{
public String quack()
{
return "呱呱叫";
}
}
class FlyWithWings implements FlyBehavior
{
public String fly()
{
return "展翅飞";
}
}
更多的整合。。。
package com.lissdy;
public class SimUDuck{
public static void main(String[] args)
{
MallardDuck duck1=new MallardDuck();
System.out.println("野鸭子出场-----------------");
System.out.println(duck1.performQuack());
System.out.println(duck1.swim());
System.out.println(duck1.display());
System.out.println(duck1.performFly());
}
}
abstract class Duck
{
QuackBehavior quackbehavior;
FlyBehavior flybehavior;
public String swim()
{
return "会游泳";
}
public abstract String display();
public String performFly()
{
return flybehavior.fly();
}
public String performQuack()
{
return quackbehavior.quack();
}
}
class Quack implements QuackBehavior
{
public String quack()
{
return "呱呱叫";
}
}
class FlyWithWings implements FlyBehavior
{
public String fly()
{
return "展翅飞";
}
}
class MallardDuck extends Duck
{
public MallardDuck()
{
quackbehavior=new Quack();
flybehavior=new FlyWithWings();
}
public String display()
{
return "外观是绿头";
}
}
当MallardDuck实例化时,它的构造器会把继承来的quackBehavior实例变量初始化成Quack类型的新实例(Quack是QuackBehavior的具体实现类),但是初始化实例变量的做法依然不够弹性。如果鸭子想在运行时动态的改变自己的行为,可以通过set来设定鸭子的行为,而不是在鸭子的构造器内实例化。
在Duck类中加入两个新方法:
public void setFly(FlyBehavior fb)
{
flybehavior=fb;
}
public void setQuack(QuackBehavior qb)
{
quackbehavior=qb;
}
可以随时调用这两个方法改变鸭子的行为。
例如:
class MallardDuck extends Duck
{
public MallardDuck()
{
quackbehavior=new Quack(); //在构造器中设定行为
flybehavior=new FlyWithWings();
}
public void display()
{
System.out.println("外观是绿头");
}
}
public class SimUDuck{
public static void main(String[] args)
{
Duck duck1=new MallardDuck();
System.out.println("野鸭子出场-----------------");
duck1.performQuack();
duck1.swim();
duck1.display();
duck1.performFly();
duck1.setFly(new FlyNoWay()); //动态的改变飞行行为
duck1.performFly();
}
}
野鸭子由展翅飞到不会飞,动态的改变了它的飞行行为。在运行时想改变鸭子的行为,只需要调用set方法就可以了。
可以把鸭子的“一组行为”想象成“一族算法”,算法代表着鸭子能做的事。
将鸭子类和行为类结合起来使用,就是组合。这种做法与继承的不同地方在于鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。
设计原则:
多用组合,少用继承。
本例使用的设计模式为策略模式,其定义为:
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。