1.简单的模拟鸭子应用:
超类Duck:呱呱叫(quack)和游泳两个方法由超类实现,因为所有的鸭子都有这个功能,display由子类自己实现,表示的是外观。
1 class Duck{ 2 quack(){} 3 swim(){} 4 abstract display(); 5 }
子类例子:
1 class MallardDuck{ 2 @override 3 display(){ 4 //外观是绿头 5 } 6 } 7 8 class RedHeadDuck{ 9 @override 10 display(){ 11 //外观是红头 12 } 13 }
2.需要增加会飞的鸭子:
joe的最想当然的做法:Duck类中添加fly()方法。
1 class Duck(){ 2 quack(){} 3 swim(){} 4 abstract display(); 5 fly(){} 6 }
问题出现了:并非所有的子类都会飞,比如子类是橡皮鸭子。所以在超类中添加某些新行为,会使得不具有该行为的子类也具有这些行为了。
joe然后进行弥补:将橡皮鸭中的fly()重写,但是什么都不做。
1 class RubberDuck{ 2 quack(){ 3 //覆盖成吱吱叫 4 } 5 display(){ 6 //外观是橡皮鸭 7 } 8 fly(){ 9 //什么都不做 10 } 11 }
问题出现了:如果以后又增加一个木头鸭呢,既不会飞也不会叫,难道也要一个一个的修改,覆盖为什么都不做吗?所以这种方法,会使得每次添加一个新类的时候,joe都要检查是否需要覆盖fly和quark方法。
于是joe觉得继承不是答案,决定改为用接口实现。建立flyable和quackable接口,需要实现这些功能的子类自己去实现接口就可以了。
1 interface Flyable{ 2 fly(); 3 } 4 5 interface Quackable{ 6 quack(); 7 } 8 9 class MallardDuck extends Duck implements Flyable, Quackable{ 10 display(){} 11 fly(){} 12 quack(){} 13 } 14 15 class RubberDuck extends Duck implements Quackable{ 16 display(){//橡皮鸭} 17 quack(){//吱吱叫} 18 } 19 20 class RubberDuck extends Duck{ 21 display(){//木头鸭} 22 }
问题出现了:因为接口不具有实现代码,所以实现接口会使得代码重复率太高。
3.合适的解决办法:
(1)为什么继承不是合适的办法:因为新增加的行为maybe不是所有子类都具有,在新增加子类时,我们还得检查,这个子类拥有父类所有的行为是否恰当,否则就要另外处理。比如木头鸭也是鸭子,但是他不叫不飞。所以不能把叫和飞放到duck类中。
(2)为什么接口不是合适的办法:因为接口不具有实现代码,所以实现接口会使得代码重复率太高。每一个子类中都要写一遍fly的实现,会疯的。
(3)解决办法的原则:找出变化之处,把它独立出来;针对接口编程,而非针对实现编程(这句话还不太懂)。
变化的部分是fly和quack,所以新建两组类(与Duck无关)FlyBehavior和QuackBehavior,再增加一个类Quiet。Duck仍然是superclass,但是fly和quack已经不在Duck里面了。
但是根据第二点原则,我们将类FlyBehavior和QuackBehavior改为接口。并且写对应的实现类。
1 interface FlyBehavior{ 2 fly(); 3 } 4 5 class FlyWithWings implements FlyBehavior{ 6 fly(){//实现鸭子飞行} 7 } 8 9 class FlyNoWay implements FlyBehavior{ 10 fly(){//什么都不做,不飞} 11 }
1 interface QuackBehavior{ 2 quack(); 3 } 4 5 class Quack() implements QuackBehavior{ 6 quack(){//实现呱呱叫} 7 } 8 9 class Squeak() implements QuackBehavior{ 10 quack(){//实现吱吱叫} 11 } 12 13 class MuteQuack() implements QuackBehavior{ 14 quack(){//不叫} 15 }
这样的话,可以让飞行和叫的行为被复用,如果要添加新行为,也不会影响现有的行为类和使用行为的鸭子类。
4.插播刚刚关于“针对接口编程,不要针对实现编程”的理解。
针对实现编程:就是前面两种方法,将行为的实现写到超类里,或者写到接口然后子类继承接口再自己写实现。这样的不好前面已经讲过了。
针对接口编程:就是第三种方法,定义行为接口,然后写行为类。将其与“使用这些行为”的duck类独立开来。
为什么要这么做:针对接口编程真正的意思是针对超类型编程,超类型可以是class也可以是interface(那为什么上面要定义为interface?),程序可以针对超类型编程,但是实现时会根据实际情况执行到真正的行为(比较拗口和难懂,看例子最生动)。
1 class Animal{ 2 abstract makeSound(); 3 } 4 5 class Dog() extends Animal{ 6 makeSound(){ 7 bark(); 8 } 9 bark(){//汪汪叫} 10 } 11 12 class Cat() extends Animal{ 13 makeSound(){ 14 meow(); 15 } 16 meow(){//喵喵叫} 17 }
针对实现编程:
1 Dog d = new Dog(); 2 d.bark;
针对接口编程:
1 Animal animal = new Dog(); 2 animal.makeSound(); 3 4 a = getAnimal(); 5 a.makeSound();
(其实还是有点懵,先这样吧)
5.整合鸭子的行为:(1)Duck将fly和quack行为委托delegate到别人那里处理,而不是用自己(Duck或者其子类)内部定义的fly和quack的具体实现。(2)在子类中设定fly和quack行为的实例变量。
1 class Duck{ 2 QuackBehavior quackBehavior; 3 FlyBehavior flyBehavior; 4 5 performQuack(){ 6 quackBehavior.quack(); 7 } 8 performFly(){ 9 flyBehavior.fly(); 10 } 11 swim(){} 12 display(){} 13 }
1 class MallardDuck extends Duck{ 2 MallardDuck(){ 3 quackBehavior = new Quack(); 4 flyBehavior = new FlyWithWings(); 5 } 6 }
然后MallardDuck是真的能够实现呱呱叫和飞行了。
但是有点小问题:我们的原则是不针对具体实现编程,但是我们现在正在结构体里制造一个具体的实现类Quack和FlyWithWings!
这会在后面改正,现在只是初始化实例变量有点不够弹性,但是整体好多了啦~(这么自问自答的精分书也是够了。)
6.实际代码:
1 public interface FlyBehavior { 2 public void fly(); 3 } 4 5 public class FlyNoWay implements FlyBehavior { 6 7 public void fly() { 8 // TODO Auto-generated method stub 9 System.out.println("i can‘t fly!"); 10 } 11 } 12 13 public class FlyWithWings implements FlyBehavior { 14 15 public void fly() { 16 // TODO Auto-generated method stub 17 System.out.println("i am flying!"); 18 } 19 }
1 public interface QuackBehavior { 2 public void quack(); 3 } 4 5 public class Quack implements QuackBehavior { 6 7 public void quack() { 8 // TODO Auto-generated method stub 9 System.out.println("Quack!"); 10 } 11 } 12 13 public class MuteQuack implements QuackBehavior { 14 15 public void quack() { 16 // TODO Auto-generated method stub 17 System.out.println("silence"); 18 } 19 20 }
1 public abstract class Duck { 2 FlyBehavior flyBehavior; 3 QuackBehavior quackBehavior; 4 5 public Duck(){} 6 public abstract void display(); 7 public void performFly(){ 8 flyBehavior.fly(); 9 } 10 public void performQuack(){ 11 quackBehavior.quack(); 12 } 13 public void swim(){ 14 System.out.println("i can swim!"); 15 } 16 } 17 18 public class MallardDuck extends Duck { 19 public MallardDuck(){ 20 quackBehavior = new Quack(); 21 flyBehavior = new FlyWithWings(); 22 } 23 public void display() { 24 // TODO Auto-generated method stub 25 System.out.println("i am a Mallard Duck!"); 26 } 27 28 }
1 public class MiniDuckSimulator { 2 public static void main(String[] args){ 3 Duck mallard = new MallardDuck(); 4 mallard.performFly(); 5 mallard.performQuack(); 6 } 7 }
输出结果是:
1 i am flying! 2 Quack!
我将Duck mallard = new MallardDuck();改为MallardDuck mallard = new MallardDuck();输出结果是一样的,那么写作duck的原因一定和之前说的“针对接口编程有关”,但是这里我还没看出来用处(应该是我笨,这本精分的书告诉我多和自己对话噗)。
7.动态设定行为:如果不想在鸭子的结构体里实例化,而是通过setter来动态设定。
1 class Duck{ 2 .... 3 public void setFlyBehavior(FlyBehavior fb){ 4 flyBehavior = fb; 5 } 6 7 public void setQuackBehavior(QuackBehavior qb){ 8 quackBehavior = qb; 9 } 10 }
8.那么如果现在要添加一个模型鸭,一套流程走下来该怎么弄呢?
(1)新建鸭子类型:ModelDuck,不会飞但是会叫的模型鸭哦~但是要给它提供一个乘坐火箭飞的技能!即使我不会飞,但是我也是有梦想的!(model duck告诉自己)
1 public class ModelDuck extends Duck { 2 3 public ModelDuck(){ 4 quackBehavior = new Quack(); 5 flyBehavior = new FlyNoWay(); 6 } 7 public void display() { 8 // TODO Auto-generated method stub 9 System.out.println("i am a Model Duck!"); 10 } 11 12 }
(2)新建新的飞行为类:FlyRocketPowered
1 public class FlyRocketPowered implements FlyBehavior { 2 3 public void fly() { 4 // TODO Auto-generated method stub 5 System.out.println("I am flying with a rocket!"); 6 } 7 8 }
(3)改变测试类,为model duck实现自己的梦想!
1 public class MiniDuckSimulator { 2 public static void main(String[] args){ 3 //用的是duck 4 Duck model = new ModelDuck(); 5 model.performFly(); 6 model.setFlyBehavior(new FlyRocketPowered()); 7 model.performFly(); 8 } 9 }
输出结果:
1 i can‘t fly! 2 I am flying with a rocket!
可以发现,对原程序需要做的改动几乎为0,而且感觉起来就很动态啊~
9.跳出duck,看看整体的格局。
我们把行为想成是“一族算法”,算法代表鸭子能做的事情(不同的叫法和飞行法),客户就可以使用封装好的飞行和呱呱叫算法族;当你将两个类结合起来用,这就是组合。设计原则:少用继承,所用组合。
这一章所学习的就是策略模式(strategy pattern):策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。