设计模式之策略模式

设计模式系列的第一站,希望自己能坚持下去。


JDK版本:9(Java8的语言特性)

参考书籍:《HEAD FIRST 设计模式》

IDE: IntelliJ IDEA


策略模式标准定义

定义了算法族,分别封装起来,让他们之间可以相互替换,初始化时将对象委托给该算法类进行行为的分配,此模式让算法的变化独立于使用算法的客户

情景(简化版本)

有一批鸭子,橡皮鸭不会飞,真鸭子会飞,设计Java类以实现这样的关系

比较粗暴的方法

定义抽象类Duck,fly方法为抽象,等待具体类去实现

public abstract class Duck{
public abstract void fly();
/*
*其他具体方法
*/
}

public class RealDuck extends Duck{
@override
public void fly(){
System.out.println("I can fly");
}

}

public class RubberDuck extends Duck{
@override
public void fly(){
System.out.println("I can't fly");
}
}

当然这样缺点很明显,如果fly内的代码要发生更改,或者要有新的Duck如RocketDuck类,就必须知晓fly方法的源码,这样显然增加了耦合度,不利于代码的维护

改进版本(书上的方法)

分析关系,不难发现除了fly方法之外,duck类的其他方法都是固定的,故可以将fly方法抽离出来,单独封装成一个接口(函数式接口)

public interface FlyBehavior {
public void fly();
}

重点部分!

我们希望把fly单独出来,所以在这里建立两个类去实现FlyBehavior接口。

public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("fly no way!");
}
}

public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("fly with wings");
}
}

注意!:

我们发现,FlyWithWings和FlyNoWay实际上是两个行为,为什么把他当作类而不是接口去处理?
这是因为接口不具有实现非静态方法的能力(在JDK8以前是如此,关于default方法之后会讲),只有类可以去实现方法。

紧接着的是出现的另一个问题:

现在fly方法已经抽离到另一个类中了,现在所需要做的就是去实现具体类RealDuck/RubberDuck 了。我们需要从Duck和FlyWithWings继承。。。

等等!Java并没有多继承功能!怎么办?

既然没有多继承功能,那我们只能“曲线救国”了
这里我们需要把FlyBehavior作为实例域(成员变量),在初始化Duck对象的时候去将FlyBehavior指向对应的实现类(FlyWithWings/FlyNoWay),这样就能正确调用实例域的fly方法了。

public class Duck {
FlyBehavior flyBehavior;

public Duck() {}
//去调用成员变量的方法
public void performFly(){
flyBehavior.fly();
}
}

public class RealDuck extends Duck {
public RealDuck() {
//在初始化的时候新建一个FlyWithWings对象,将flyBehavior指向它
flyBehavior = new FlyWithWings();
}
//用设定方法来规定鸭子行为
public void setFlyBehavior(FlyBehavior other){
flyBehavior = other
}
}

这里实际上实现的是委托的效果:Duck类将fly方法委托给flyBehavior去做,而flyBehavior指向的是具体的类(FlyWithWings/FlyNoWay),如此完成类似多继承的操作。

体现思想

我们把行为想象成是“一族算法”,在本例中,算法代表鸭子能做的事(不同的飞行法),客户使用封装好的算法族,不需要知道其具体实现,得到解耦的效果

设计原则

  1. 面向接口编程,而不面向具体实现
  2. 多用组合,少用继承

default解法(自己的一点想法)

前文说到我们将FlyWithWings/FlyNoWay设计成类是因为只有类才可以去实现方法,但在jdk8中新增的defalut修饰符可以让接口有了实现非静态方法的能力

public interface FlyWithWings extends FlyBehavior{
@Override
//继承并重写了FlyBehavior的fly方法
defalut public void fly() {
System.out.println("fly with wings");
}
}

public class RealDuck extends Duck implements FlyWithWings{
@Override
public void performFly() {
fly();
}

}

public abstract class Duck {
public abstract void performFly();
}

default的加入使得接口彻底的成为了一个没有成员变量的特殊类,使得类多继承变得更加方便。

但是default看似美好,却无形中增加了耦合度,现在,每个鸭子的fly方法被顶死,我们无法在运行时改变鸭子的fly状态(比如从能飞到不能飞)这恐怕并不是什么好主意

函数式接口写法

jdk8中定义,只含有一个抽象方法的接口为函数式接口,熟悉函数式语言的同学都知道,函数式最大的优点就是简洁,让我们来看看是如何简化的

public class RealDuck extends Duck {
public RealDuck() {
flyBehavior = ()-> System.out.println("i can fly");
}
}

有没有一种梦回ES6的感觉?FlyBehavior是函数式接口,不妨就将其看成函数,该函数指向一个lambda表达式(又称箭头函数/匿名函数),如此就完成了类似于之前方法的new 一个FlyWithWings的操作!

最关键的是,我们不再需要建立一个FlyBehavior的实现类。

Tips:这在处理小范围代码的时候较为方便,当代码量一多,还是去把类写出来比较方便

总结

策略模式是设计模式的第一课,其核心是将经常变动的业务代码分离开来,封装成另外一个行为类处理。把该行为类对象作为大类的成员变量,在初始化时把该行为委托给行为类对象进行分配,从而让行为匹配正确。

Author: Gofun4
Link: https://www.gofun4.top/2018/06/06/设计模式之策略模式/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.