策略模式
策略模式也是23种设计模式之一。
策略模式是指将程序中可变部分抽象分离成一系列的算法,并将每一个算法封装起来,而且使他们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
策略模式一般由下面三部分组成:
1. 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
2. 具体策略角色:包装了相关的算法和行为。
3. 环境角色:持有某一个策略类的引用,客户端调用。
策略模式设计原则:
1. 把程序中需要变化的部分抽离出来,独立于不变的部分。
2. 面向接口编程,而不是面向实现编程,多用组合,少用继承。
(组合 :在类中增加一个私有域,引用另外一个已经有的类的实例,通过调用实例的方法从而获得新的功能)
我们以鸭子(Duck)做一个例子。
鸭子有哪些行为呢?
行为:游泳,飞。
所有的鸭子都会游泳我们是知道的,但是所有的鸭子都会飞嘛?
真鸭子会飞,那假鸭子呢?
答案是否定的,依照我们策略设计模式的原则,我们需要把程序中需要变化的部分抽离出来,也就是要把鸭子会飞的行为抽离出来。
在抽离之前,我们先来把不需要分离的部分写出来。
鸭子都会游泳,是一个共有的行为,那么我们把游泳的行为写进抽象鸭子类(Duck)中。
Duck.java代码:
public abstract class Duck {
public void swing(){
System.out.println("我会游泳!!!");
}
public void play(){
swing();
}
}
共有的行为写完了,那么我们就来写飞的行为,因为有些鸭子会飞,有些鸭子不会飞,所以飞行行为的实现是不同的。
我们创建一个FlyBehavior的接口,让子类去实现不同的行为。
FlyBehavior.java代码:
public interface FlyBehavior {
public void fly();
}
然后我们再创建两个类来继承这个接口,实现不同的飞行行为。
FlyWithWing.java代码:
public class FlyWithWing implements FlyBehavior{
@Override
public void fly() {
System.out.println("我会飞!!!");
}
}
FlyNoWay.java代码:
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("很遗憾,我飞不起来。。。");
}
}
这下子,我们把鸭子飞的行为也写完了,那么我们是不是需要把它们组合起来呢。
再次申明下组合的概念:
组合 :在类中增加一个私有域,引用另外一个已经有的类的实例,通过调用实例的方法从而获得新的功能。
我们需要在Duck类中增加一个私有域,引用FlyBehavior的实例,通过调用它的fly();方法来获得相应的飞行能力。
我们重新修改下Duck类。
修改后的Duck.java代码:
public abstract class Duck {
private FlyBehavior flyBehavior;
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void fly(){
flyBehavior.fly();
}
public void swing(){
System.out.println("我会游泳!!!");
}
public void play(){
swing();
fly();
}
}
flyBehavior属性就是引用的那个私有域,我们为这个属性写上set方法的原因是为了之后能够更好的根据我们的需求而变化。
在fly();方法里面我们调用了flyBehavior的fly();方法,这样我们通过实例化FlyBehavior接口的不同子类来实现flyBehavior.fly()的动态改变。
接下来我们写一个真鸭子的子类来继承抽象Duck类。
RealDuck.java代码:
public class RealDuck extends Duck{
public RealDuck(){
super.setFlyBehavior(new FlyWithWing());
}
}
我们把假鸭子的代码也写出来。
RubberDuck.java代码:
public class RubberDuck extends Duck{
public RubberDuck(){
super.setFlyBehavior(new FlyNoWay());
}
}
这样下来,我们的真鸭子和假鸭子都通过自己的构造方法为flyBehavior属性实例化了不同的飞行行为。
我们用真鸭子为例,来测试下,新建一个DuckTest类。
DuckTest.java代码:
public class DuckTest {
public static void main(String[] args){
RealDuck realDuck=new RealDuck();
realDuck.play();
}
}
控制台输出结果:
我会游泳!!!
我会飞!!!
我们不需要知道飞行行为的具体实现,我们只需要根据自己的需求实例化不同的FlyBehavior接口子类就OK了。
通过这个实现,我们让可变的飞行行为独立于鸭子类本身,这样可便于后期的维护和拓展。
比如我们现在想写一只超级鸭子,它具有火箭推动的飞行能力,在我们之前的FlyBehavior接口子类中并没有这个飞行行为。
那么我们只需要重新写一个火箭推动的飞行类即可。继承FlyBehavior接口。
FlyWithRocketPower.java代码:
public class FlyWithRocketPower implements FlyBehavior{
@Override
public void fly() {
System.out.println("我用火箭来推动我飞行!!!");
}
}
火箭推动的飞行行为我们写好了,然后再写一只超级鸭子即可。
SuperDuck.java代码:
public class SuperDuck extends Duck{
public SuperDuck() {
super.setFlyBehavior(new FlyWithRocketPower());
}
}
我们再来测试一下超级鸭子,修改下测试类就好了。
修改后的DuckTest.java代码:
public class DuckTest {
public static void main(String[] args){
SuperDuck superDuck=new SuperDuck();
superDuck.play();
}
}
控制台输出结果:
我会游泳!!!
我用火箭来推动我飞行!!!
这让我们在后期需求发生变化或者要求新的功能时,大大减少我们的代码量,我们只需要实现相应的接口即可。
让我们面向接口编程。
这就是策略模式!