最近在学习设计模式,看那本HeadFirst的设计模式,讲的很是生动,详细讲解了几个常用的设计模式,对与入门新手,收益良多,这系列文章,总结一下我的学习成果,基于代码实现还有我个人的一个理解。
这篇博客,谈谈策略模式。
先简述一下,这篇文章思路,是以一个duck类为主体,不同的duck子类有不同的行为,也有相同的行为,并且可以给所有duck子类统一添加行为,也可以给某一个子类特定添加或修改某一行为。
对于开发经验不足的人来说,首先我们会想到定义一个父类,然后子类继承或重写父类公共方法,并添加自己特定的方法。代码如下:
public abstract class Duck { //所有鸭子都会叫,都会游泳,所以超类直接实现这部分代码 public void quack(){ System.out.println("I can quack guaguagua"); } public void swim(){ System.out.println("I can swim"); } //不同的鸭子形态不同,所以为抽象方法,子类自己实现 public abstract void display(); }
public class GreenheadDuck extends Duck { @Override public void display() { System.out.println("I have green head"); } }
public class RedheadDuck extends Duck { @Override public void display() { System.out.println("I have red head"); } }
很容易就实现了,毫无压力。
但是现在需求更改了,要求鸭子要会飞。很多人会想到给超类添加fly()方法。但是如果不是让所有鸭子都会飞呢。比如一个橡皮鸭(RubberDuck),其他鸭子都会飞,但是它不能会飞。oh,对,可以在RubberDuck类中,把父类的fly()重写掉,里面没有任何的实现代码。是个方法.
但是如果需求改为添加一个木头鸭子,它不能飞还不能叫,怎么办?重写fly()和quack()?
这显然不是好的解决方案。
tip1:利用接口怎么样?有一个flyable接口,一个quackable接口,会叫的会飞的分别实现对应接口。这样可以实现我们的预期功能。当你真正去实现这个想法的时候,你发现,当你有40个子类的时候,其中三十个的飞行方式都是一样的,但是你就得重写三十次飞行方法。这种做法是可笑的。
程序设计时,有一个设计原则,可以让你找到思路:
找出应用中可能要变化的地方,把它们独立出来,不要和不会变化的代码混合。
现阶段的需求中,可能变化的部分是鸭子叫的方式,飞的方式。为了把这两个行为从类中分离出来,我们新建一组新类,来代表每一个新的行为,如何设计呢?另一个设计原则会给我们思路:
针对接口编程,不要针对实现编程。
我们会设计多个接口,用来实现各种行为,然后特定的行为,不同的实现对应接口里的方法。与之前不同的是,这次是特定的行为类,在鸭子子类中,我们会通过一个变量实例化特定行为类,调用行为类中的方法,用来实现不同鸭子子类的不同行为。像这样(只演示飞行):
public interface FlyBehavior { void fly(); }
public class FlyWithWings implements FlyBehavior { @Override public void fly() { System.out.println("I can fly"); } }
public class FlyWithNothing implements FlyBehavior { @Override public void fly() { System.out.println("I can't fly"); } }
接下来的问题在于怎么在duck类中调用不同的行为方法。因为我们的思路是所有的子类都调用fly,只是不能飞行的鸭子实现代码中没有内容。所以我们可以在超类中添加如下代码:
FlyBehavior flyBehavior; public void performQuack(){ flyBehavior.fly(); }
我们用performQuack()方法替代fly()方法;这样调用方法的过程委托给了行为类,
而且可以再运行时自主调用performQuack()方法。
public interface FlyBehavior { void fly(); }
我们需要在子类实现中添加下面代码,用来确定特定行为类
//红鸭子会飞,所以实例化FlyWithWings类
public RedheadDuck(){ flyBehavior=new FlyWithWings(); }
//橡皮鸭子不会飞,所以实例化FlyWithNothing类
public RubberDuck(){ flyBehavior=new FlyWithNothing(); }
测试代码:
public static void main(String[] args) { Duck redduck=new RedheadDuck();
redduck.performQuack();
redduck.swim();
redduck.display();
Duck rubberDuck=new RubberDuck();
rubberDuck.performQuack();
rubberDuck.swim();
rubberDuck.display();
}
结果:
I can fly
I can swim
I have red head
I can't fly
I can swim
I have Rubber head
有些经验的人会发现,我们平时的类不希望它所有的方法都是写死的,尤其在我们提取出的随时可能会改变的部分,我们更希望在运行时,由我们自己决定类中方法。所以我们在子类中直接实例化行为类是不可取的。
我们在超类中添加行为接口的get set方法
public FlyBehavior getFlyBehavior() { return flyBehavior; } public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; }
这样我们在运行时如果想要改变某个子类的行为,可以直接设定
Duck redduck=new RedheadDuck();
redduck.performQuack();
//动态改变红头鸭子行为,使其不能飞行
redduck.setFlyBehavior(new FlyWithNothing());
redduck.performQuack();
redduck.swim(); redduck.display();
结果:
I can fly
I can't fly
I can swim
I have red head
经过上面一系列历程,我们解决的面对的一些问题,可以自由的增添子类,动态的改变特定方法。让我们回顾一下我们这次程序设计的一些亮点。
1.我们把可能会改变的每种鸭子的行为用一个行为类进行了封装,不会改变的方法在超类中进行了实现。当一种鸭子的行为要发生改变的时候,我们可以在运行时动态的用另一个行为类进行替换;——这也是策略模式的中心思想。
2.我们针对接口编程
所谓针对接口,并不是真正意义上的interface,可以使一个抽象类。重点使用多态就对了。
本例中,我们Duck redduck=new RedheadDuck(); redduck.performQuack();
这样你不用管performQuack里面是什么东西,你只需要知道,是一个鸭子,就要调用这个方法就够了。
之前我们会 RedheadDuck redheadDuck=new RedheadDuck(); redheadDuck.fly();
甚至我们不需要在类中硬编码实例化新的子类,可以 duck=getDuck(); duck.performQuack();这样在运行时具体制定要实现的对象。
3.我们使用组合,而非继承。
所谓组合就是将两个或者更多的类在一个类中使用,在当前类中调用其他类的方法,而不是继承来所有的方法。这样可以使程序更具有弹性,随时调用你想用的任意方法。而“tip1”中的思想相当于每个类都实现一个接口,重写其中方法,这使得程序变得难以修改,而我们在超类中
FlyBehavior flyBehavior;
public void performQuack(){
flyBehavior.fly();
}
然后在子类中确定FlyBehavior的具体实现类,相当于调用了另一个类的fly()方法。这样我们不仅可以将fly()中的代码封装起来,更可以在运行时动态的改变行为。
再看看策略模式的定义:定义一个算法族(指一系列的算法),分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
本例中的代码都很简单,但设计模式是思想,学习的路还很远~