@DevWiki
2015-07-02T13:15:20.000000Z
字数 4989
阅读 2436
设计模式
更多内容详见:Java设计模式学习笔记
最近看了Head First 设计模式一书,开篇的故事讲述了设计模式的原则:封装变化与面向接口编程.
故事从编写一个模拟鸭子的游戏开始,游戏要求:
游戏里有许多鸭子,一边游泳戏水,一边呱呱叫...
该游戏内部使用面向对象设计,有一个鸭子的超类Duck:
public abstract class Duck{public void swim(){//游泳的方法}public void quack(){//呱呱叫的方法}public abstract void display(){//子类要实现的显示的方法}}
因为所有的鸭子都会游泳和叫,所以在超类中实现了swim()和qucak()方法,而具体显示出什么样和具体的鸭子有关,所以display()方法为抽象方法.
现在有种鸭子是红头鸭RedHeadDuck和绿头鸭MallardDuck.
红头RedHeadDuck代码:
public class RedHeadDuck extends Duck {public void display() {System.out.println("我是红头鸭...");}}
绿头鸭MallardDuck代码:
public class MallardDuck extends Duck {public void display() {System.out.println("我是绿头鸭...");}}
现在需求发生了变化,想要鸭子能飞行...那不是很简单嘛,给Duck类加个飞行的方法不就可以了,如下:
public abstract class Duck{public void swim(){//游泳的方法}public void quack(){//呱呱叫的方法}public void fly(){//飞行的方法}public abstract void display(){//子类要实现的显示的方法}}
这样一来,确实绿头鸭和红头鸭都会飞行了.
由于公司业务需要,增加橡皮鸭这一角色RubberDuck,如下:
public class RubberDuck extends Duck {public void display() {System.out.println("我是橡皮鸭...");}}
等等,上面的橡皮鸭貌似不对啊,橡皮鸭不会飞啊!而且橡皮鸭是吱吱叫不是呱呱叫.这该怎么办呢?
这还不简单,直接覆盖方法不就行了.
public class RubberDuck extends Duck {public void qucak(){//吱吱叫...}public void fly(){//什么也不做...}public void display() {System.out.println("我是橡皮鸭...");}}
这样貌似是解决了,但是问题又来来,如果后来需要增加诱饵鸭DecoyDuck,诱饵鸭不会叫不会飞.怎么办?难道还要继续覆盖方法么?
既然无法确定以后的鸭子是什么类型,干脆抽取公共的部分,不同的写成接口.
比如会飞的实现Flyable接口,会叫的实现Qucakable接口.
//会飞的接口public interface Flyable{void fly();}//会叫的接口public interface Quackable{void quack();}//新的Duck类public abstract class Duck{public void swim(){//游泳的方法}public abstract class display(){//显示的方法}}//新的绿头鸭public class MallardDuck extend Duck implements Flyable, Qucakable {public void fly(){//我会飞...}public void quack(){//我会呱呱叫...}public void display(){//我是绿头鸭}}//新的红头鸭类public class RedHeadDuck extend Duck implements Flyable, Qucakable {public void fly(){//我会飞...}public void quack(){//我会呱呱叫...}public void display(){//我是红头鸭}}//橡皮鸭public class RubberDuck extend Duck implements Quackable {public void quack(){//我会吱吱叫...}public void display(){//我是橡皮鸭}}//诱饵鸭public class DecoyDuck extends Duck {public void display(){//我是诱饵鸭}}
这样一来,问题就解决了.
上面的问题是解决了,好像代码有重复:
绿头鸭和红头鸭的会飞的方法和会呱呱叫的方法是重复的.
如果以后有更多类型的方法,重复的代码会更多,而且会埋下一个隐患:
如果以后飞行的动作有所改变,难道一个一个类的去修改?
如果需求还有变化,不是更难维护吗?
有没有好的方法解决这个问题呢?答案是肯定的.我们需要将代码中的变化的部分与不变的部分拆分出来.这就是封装变化的原则
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
下面就建立两组类,变化的和不会变化的.
上面的案例中什么是变化的呢?
飞行和叫声是变化的.那么就将飞行和叫声与Duck类分开.
如何设计鸭子的飞行行为和叫声行为呢?
我们希望一切有弹性,因为你无法确定以后的飞行行为会有什么变化,也无法确定以后的绿头鸭会有什么行为.
这就涉及到第二个原则:面向接口编程
针对接口编程,而不是针对实现编程
那么现在的需求有两个行为:飞和叫.
接口就为飞行行为接口和叫的行为接口:
//飞行行为接口public interface FlyBehaviour{void fly();}//叫的行为接口public interface QuackBehaviour{void quack();}
现在飞行有种不同的行为:飞和不会飞.
//普通的飞public class FlyWithWings implements FlyBehaviour {public void fly(){System.out.println("我会飞...");}}//不会飞public class FlyNoWay implements FlyBehaviour {public void fly(){//我不会飞...}}
现在叫也有三种行为:呱呱叫和吱吱叫和不会叫
//呱呱叫public class Quack implements QuackBehaviour {public void quack(){System.out.println("我会呱呱叫...");}}//吱吱叫public class Squack implements QuackBehaviour {public void quack(){System.out.println("我会吱吱叫...");}}//不会叫public class MuteQuack implements QuackBehaviour {public void quack(){//我不会叫...}}
这样写的好处就在于,使用飞行行为时只需指定会飞行,不需绑定具体飞行的动作,弹性空间较大.而且此处的面向接口编程,并不是狭义上指Java中的接口,而是指超类型,可以是接口也可以是抽象类.
那么如何将行为和Duck类组合到一起呢?
将行为转为属性
即将飞行和叫的行转为鸭子的一个变量
public abstract class Duck {//鸭子不处理飞的行为,将飞的行为委托给FlyBehaviour接口FlyBehaviour flyBehaviour;//鸭子不处理叫的行为,将飞的行为委托给QucakBehaviour接口QucakBehaviour quackBehaviour;public void performFly(){flyBehaviour.fly();}public void performQuack(){quackBehaviour.quack();}public void swim(){System.out.println("我会游泳...");}public abstract void display();}
再来看看绿头鸭,
public class MallardDuck extends Duck {public MallardDuck(){flyBehaviour = new FlyWithWings();quackBehaviour = new Quack();}public class void display(){System.out.println("我是绿头鸭...");}}
现在测试一下:
public class Client{public static void main(String[] args){Duck duck = new MallardDuck();duck.display();duck.performFly();duck.performQuack();duck.swim();}}
执行后结果如下:
我是绿头鸭...我会飞...我会呱呱叫...我会游泳...
如何实现动态改变鸭子的行为呢?修改Duck类如下:
public abstract class Duck {//鸭子不处理飞的行为,将飞的行为委托给FlyBehaviour接口FlyBehaviour flyBehaviour;//鸭子不处理叫的行为,将飞的行为委托给QucakBehaviour接口QucakBehaviour quackBehaviour;public void setFlyBehaviour(FlyBehaviour flyBehaviour){this.flyBehaviour = flyBehaviour;}public void setQucakBehaviour(QucakBehaviour quackBehaviour){this.quackBehaviour = quackBehaviour;}public void performFly(){flyBehaviour.fly();}public void performQuack(){quackBehaviour.quack();}public void swim(){System.out.println("我会游泳...");}public abstract void display();}
现在构建一个模型鸭ModelDuck
public class ModelDuck extends Duck{public ModelDuck(){flyBehaviour = new FlyNoWay(); //一开始不会飞quackBehaviour = new Quack();}public void display(){System.out.println("我是模型鸭...");}}
新建一个新的飞行行为:FlyRocketPowered
public class FlyRocketPowered implements FlyBehaviour{public void fly(){System.out.println("我能像火箭一样飞...");}}
现在测试一下动态改变飞行行为:
public class Client{public static void main(String[] args){Duck duck = new ModelDuck();duck.display();duck.performFly();duck.setFlyBehaviour(new FlyRocketPowered());duck.performFly();}}
测试结果:
我是模型鸭...我会向火箭一样飞...
这样就实现了行为与类分开,及变化的部分与不变化的部分分开了.
变化的部分
飞行的行为和叫的行为
不变的部分
鸭子会有用,拥有飞行和叫的行为.
封装变化和面向接口编程能让代码有很大的弹性,在代码不变或者很小的改变的情况下满足需求的变化,也易于维护.
更多内容请移步我的博客:DevWiki的博客