接着我的上一篇文章策略模式学习1。
让我们来回顾一下上面设计中橡皮鸭类的代码,并提出一下问题。
/**
* 橡皮鸭
* @author Administrator
*
*/
public class RubberDuck extends Duck {
@Override
void display() {
System.out.println("橡皮鸭...");
}
/*
* 橡皮鸭子不会呱呱叫
* 但是问题来了,这个橡皮鸭子会飞?
* 那设计人员会说:一样覆盖掉就好
*/
@Override
public void quack() {
//覆盖超类的呱呱叫
System.out.println("吱吱叫...");
}
@Override
void fly() {
//覆盖,变成什么事儿都不做
;
}
/*
* 那么问题来了,以后每个月更新,常常会出现新的鸭子子类,这要被迫每次检查
* 并可能覆盖quack() 和 fly() 这简直就是无穷无尽的噩梦!!!!
* 这样设计的缺点:
* 1.代码在多个子类中重复
* 2.运行时的行为不容易被改变
* 3.改变会牵一发动全身,造成其他鸭子不想要的改变
* 4.很难知道鸭子的全部行为,因为有了不属于它本身的行为。
*/
//重复代码变多,那么你认为覆盖几个方法就算是差劲,那么对于48个Duck子类都要稍微修改一下
//飞行的行为,你又怎么说?需求:会飞的鸭子中,飞的动作可能有多种变化.
}
因此我们可以看到,利用继承来提供Duck的行为,会导致以上所述的问题以及缺点。
那利用接口如何?将飞行方法和呱呱叫的方法做成一个接口,分别让具备这两个行为的鸭子实现这个接口,就能够解决不再会有会飞的鸭子。
但是缺点是:造成了代码无法复用(虽然鸭子类中实现了飞行的方法,
但是这个鸭子在某个场景下又想要其他类型的飞行方法呢?这有点类似于游戏中的升级,解锁了某技能或在某个副本中,会以新的行为出现)。这又跳进了另外一个坑。
另外,如果飞行的动作可能还有多种变化呢?比如火箭式飞行,螺旋式飞行等等。
软件开发的一个不变的真理:改变(change).不管软件设计得多好,一段时间之后,总是需要成长和改变,否则软件就会“死亡”。
利用Flyable和Quackable一开始似乎还不错,解决了只有会飞的鸭子才实现Flyable,但是java接口不具有实现代码,所以实现接口无法达到代码的复用。这就意味着无论何时你需要修改某个行为,你必须得往下追踪到并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误。
幸运的是,这里有一个设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。换句话说:如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。对这个原则的另外一种思考方式:把会变化的部分取出并封装出来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。
我们知道Duck类的fly()和quack()会随着鸭子的不同而改变。因此我们把这两个行为从Duck类中分离出来,建立一组新类来代表每个行为。
如何设计鸭子的行为?我们想要产生一个新的绿头鸭实例,并指定特定的“类型”的飞行行为给它。那么干脆顺便让鸭子的行为可以动态的改变好了。换句话说:我们应该在鸭子类中包含设定行为的方法,这样就可以在“运行时”动态地“改变”绿头鸭的飞行行为。
设计原则:针对接口编程,而不是针对实现编程。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
// 行为变量被声明为行为 "接口" 类型
public void swim() {
System.out.println("游来游去...");
}
abstract void display();
//鸭子对象不亲自处理呱呱叫行为,而是委托给quackBehavior引用的对象去执行
void performQuack() {
quackBehavior.quack();
}
void performFly() {
flyBehavior.fly();
}
public FlyBehavior getFlyBehavior() {
return flyBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public QuackBehavior getQuackBehavior() {
return quackBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
/*public void quack() {
System.out.println("呱呱叫...");
}
void fly(){
System.out.println("飞来飞去...");
}*/
//鸭子的其他方法
}
public interface FlyBehavior {
void fly();
}
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("这里实现了所有有翅膀的鸭子飞行动作...");
}
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
//什么都不做,不会飞
System.out.println("I can't fly!");
}
}
public class FlyByRocky implements FlyBehavior {
@Override
public void fly() {
// 新增了一个火箭式的飞行
System.out.println("火箭式的飞行...");
}
}
public interface QuackBehavior {
void quack();
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
// 实现鸭子呱呱叫
System.out.println("呱呱叫...");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
// 橡皮鸭子吱吱叫
System.out.println("吱吱叫...");
}
}
public class MallardDuck extends Duck {
@Override
void display() {
System.out.println("绿头鸭..");
}
}
public class ModelDuck extends Duck {
public ModelDuck(){
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new MuteQuack();
}
@Override
void display() {
// 这是一只模型鸭
System.out.println("模型鸭...");
}
}
// 新增一个鸭鸣器,用来捕获其他真正鸭子
public class DuckCall implements QuackBehavior{
QuackBehavior quack;
public QuackBehavior getQuack() {
return quack;
}
public void setQuack(QuackBehavior quack) {
this.quack = quack;
}
@Override
public void quack() {
quack.quack();
}
}
//测试类
public class MiniDuckSimulator {
public static void main(String[] args) {
/*Duck mallard = new MallardDuck();
mallard.setFlyBehavior(new FlyWithWings());
mallard.setQuackBehavior(new Quack());
mallard.performFly();
mallard.performQuack();*/
/*Duck modelDuck = new ModelDuck();
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyByRocky());//模型鸭可以动态地改变飞行行为
modelDuck.setQuackBehavior(new MuteQuack());
modelDuck.performFly();*/
//实现一个鸭鸣器用来诱捕野鸭
DuckCall duckCall = new DuckCall();
duckCall.setQuack(new Quack());
duckCall.quack();//利用多态,可以动态的模拟各种叫声
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
// 什么都不做,不会叫
;
}
/*
* 这样设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这样行为已经与鸭子无关了
* 而我们可以新增一些行为,不会影响到既有的行为类,不会影响“使用”到飞行行为的鸭子类了。
* 有了继承的“复用”的好处,却没有了继承所带来的包袱
*/
}
这里也用到另外一个设计原则:多用组合,少用继承。
“有一个”可能比“是一个”更好。有一个关系相当有趣,每个鸭子都有一个FlyBehavior和QuackBehavior,当你将两个类结合起来使用,如通本例一般,这就是用到了组合。