版权声明:本文由 Hov 所有,发布于 https://chenhy.com ,转载请注明出处。
1 策略模式
1.1 简介
策略模式:通过定义算法族,封装不同算法的具体实现。使用算法的用户只需调用已封装好的算法即可,无需重写算法代码,增加了算法代码的复用性。同时,可以动态选择特定的算法实现。
1.2 思路
策略模式将每一个算法封装起来,实现算法族(业务规则),这些算法可互换代替。
策略模式通过分离变化部分,封装接口,基于接口实现不同的功能。也就是说,对于同一类型的操作,将复杂多样的处理方式分离开来,有选择的实现各自特有的操作。通常,在超类放行为接口对象,在子类设定具体行为对象。
1.3 使用场景
对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如,买东西要付款,但是付款的方式有多种,比如有的人现金支付,有的人网上支付,有的人找人代付。
- 对于同一类型问题的多种处理方式,仅仅具体行为有差别。
- 需要安全的封装多种同一类型的操作。
- 出现同一抽象多个子类,又需要使用 if 或 switch 选择语句来选择。
1.4 优缺点
优点:
- 算法可自由切换;
- 避免使用多重条件判断;
- 扩展性良好。
- 可替换继承关系
缺点:
- 策略类会增多;
- 所有策略类都需要对外暴露。
2 实例讲解
我们引入模拟鸭子这个例子(这是 Head First设计模式中的例子,简单易懂,推荐)。
2.1 原始需求
现在,我们要通过程序模拟鸭子。假设我们的需求是这样的:鸭子有绿头鸭和红头鸭两种,它们都会嘎嘎叫和游泳。
根据需求,借助面向对象思维,我们可以设计如下代码:
(1)首先设计一个抽象类:Duck
/* Duck.java */
public abstract class Duck{
public Duck(){
}
public abstract void display(); //外观
public void quack(){ //嘎嘎叫
System.out.println("--gaga--");
}
public void swim(){ //游泳
System.out.println("--swimming--");
}
}
(2)绿头鸭 GreenHeadDuck 继承抽象父类 Duck
因为父类 Duck 中有抽象方法 display ,所以子类 GreenHeadDuck 需要重写 display 方法,这里我们通过重写 display 方法实现了不同鸭子(子类)外观的差异化。
/* GreenHeadDuck.java */
public class GreenHeadDuck extends Duck{
@Override
public void display(){
System.out.println("**GreenHeadDuck**"); //我是绿头鸭
}
}
(3)同理,红头鸭也抽象父类 Duck
因为父类 Duck 中有抽象方法 display ,所以子类 RedHeadDuck 需要重写 display 方法,这里我们通过重写 display 方法实现了不同鸭子(子类)外观的差异化。
/* RedHeadDuck.java */
public class RedHeadDuck extends Duck{
@Override
public void display(){
System.out.println("**RedHeadDuck**"); //我是红头鸭
}
}
(4)模拟绿头鸭、红头鸭
/* StimulateDuck.java */
public class StimulateDuck {
public static void main(String[] args) {
GreenHeadDuck mGreenHeadDuck = new GreenHeadDuck(); //绿头鸭
RedHeadDuck mRedHeadDuck = new RedHeadDuck(); //红头鸭
//绿头鸭出场
mGreenHeadDuck.display();
mGreenHeadDuck.quack();
mGreenHeadDuck.swim();
//红头鸭出场
mRedHeadDuck.display();
mRedHeadDuck.quack();
mRedHeadDuck.swim();
}
}
程序输出结果如下:
绿头鸭嘎嘎叫和游泳,红头鸭嘎嘎叫和游泳。
**GreenHeadDuck**
--gaga--
--swimming--
**RedHeadDuck**
--gaga--
--swimming--
可见,我们通过程序成功模拟鸭子。
嘎嘎叫和游泳是所有鸭子的共同技能,但是鸭子的外观(种类)有可能不同。
所以,在超类 Duck 将 display 方法定义成抽象方法,由子类(不同鸭子)去实现 display 方法,实现了不同鸭子的差异化。
2.2 新的需求
前面我们实现了不同鸭子可以有不同的外观(绿头鸭、红头鸭),而且都能嘎嘎叫和游泳。现在我们有了新的需求,我们要让一些鸭子会飞。
(1)按照前面的解决方案:像游泳这个技能一样,在超类 Duck 中增加会飞这个技能,这样继承自超类 Duck 的鸭子都会飞。
修改 Duck.java 代码如下:
/* Duck.java */
public abstract class Duck{
public Duck(){
}
public abstract void display(); //外观
public void quack(){ //嘎嘎叫
System.out.println("--gaga--");
}
public void swim(){ //游泳
System.out.println("--swimming--");
}
/* 增加 fly 方法 */
public void fly(){ //飞
System.out.println("--flying--");
}
}
但是,这样会带来比较大的问题,继承自这个超类的鸭子都会飞。
这也是继承存在的问题:对类的局部改动,尤其超类的局部改动,会影响其它部分。
事实上,并不是所有的鸭子都会飞,所以我们能不能让有的鸭子会飞,有的鸭子不会飞呢。
(2)还是按照之前的思路来解决上面的问题:我们在子类中重写 fly 方法,比如绿头鸭不会飞,我们就在绿头鸭这个子类中重写 fly 方法。
修改 GreenHeadDuck.java 代码如下:
/* GreenHeadDuck.java */
public class GreenHeadDuck extends Duck{
@Override
public void display(){
System.out.println("**GreenHeadDuck**");
}
/* 重写超类 Duck 中的 fly 方法 */
@Override
public void fly() { //不会飞
System.out.println("--can not fly--");
}
}
这样就可以让有些鸭子能飞,有些鸭子不能飞了。
遗憾的是,这样降低了代码的复用性。
如果我们又新增一类鸭子,那是不是在子类上也重写 fly 方法呢?
显然,每次增加新的鸭子,我们可能就需要重写 fly 方法或其它方法,来实现不同鸭子的差异化。
2.3 策略模式
在新的需求下,我们要让有的鸭子能飞,有的鸭子不能飞。
在 2.2 中,我们遗留了一些问题:
- 如果只在超类中定义新的 fly 方法,那么所有鸭子都会飞,不符合要求;
- 在超类定义 fly 方法,在子类重写 fly 方法。但是,每新增一个子类(新的鸭子)我们都要重写 fly 方法,代码复用性不高,而且操作繁琐。
有了策略模式,上面的问题不复存在。
(1)通过接口封装行为(算法族)
FlyBehavior 接口(算法族):飞行行为族
/* FlyBehavior.java */
public interface FlyBehavior { // FlyBehavior 行为族
void fly();
}
GoodFlyBehavior 类(算法实现类)实现 FlyBehavior 接口(算法族):会飞
/* GoodFlyBehavior.java */
public class GoodFlyBehavior implements FlyBehavior { //会飞
@Override
public void fly() {
System.out.println("--GoodFly--");
}
}
BadFlyBehavior 类(算法实现类)实现 FlyBehavior 接口(算法族):不会飞
/* BadFlyBehavior.java */
public class BadFlyBehavior implements FlyBehavior { //不会飞
@Override
public void fly() {
System.out.println("--BadFly--");
}
}
GoodFlyBehavior 、 BadFlyBehavior 类都实现了 FlyBehavior 接口。
我们通过 FlyBehavior 接口将飞行行为封装起来, FlyBehavior 就是我们所说的行为族(算法族)。
GoodFlyBehavior 、 BadFlyBehavior 算法实现类是 FlyBehavior 行为族下的不同行为策略,分别对应会飞、不会飞。
GoodFlyBehavior 、 BadFlyBehavior 算法实现类可被用来实例化不同的鸭子,这样就可以让鸭子有不同的行为策略,即有的鸭子会飞,有的鸭子不会飞。
(2)修改超类 Duck
接下来,我们需要修改父类 Duck ,以符合策略模式。值得注意的是,以后我们新增鸭子时不需要修改父类 Duck 的代码,因为父类 Duck 已经按照策略模式封装好代码。
/* Duck.java */
public abstract class Duck{
FlyBehavior mFlyBehavior; //声明 FlyBehavior 接口的引用变量
public Duck(){
}
public void quack(){
System.out.println("--gaga--");
}
public abstract void display();
public void swim(){
System.out.println("--swimming--");
}
//注意这里我们没有设定鸭子会不会飞,而是让 mFlyBehavior 去考虑
public void fly(){
mFlyBehavior.fly();
}
//动态设置 FlyBehavior ,这里可根据 Duck 的子类来动态设置行为族
public void SetFlyBehavior(FlyBehavior fb) {
mFlyBehavior = fb;
}
}
(3)在子类设定绿头鸭、红头鸭会不会飞
因为 GreenHeadDuck 和 RedHeadDuck 都继承了 Duck ,所以它们也继承了 FlyBehavior 类型的 mFlyBehavior 变量。
我们可以在它们的构造器中根据不同的飞行行为来实例化 mFlyBehavior。不同的 FlyBehavior 实例对应不同的飞行行为,比如 GoodFlyBehavior(会飞)、BadFlyBehavior(不会飞)。
/* GreenHeadDuck.java */
public class GreenHeadDuck extends Duck{
public GreenHeadDuck() {
mFlyBehavior = new GoodFlyBehavior(); //实例化为 GoodFlyBehavior(会飞)
}
@Override
public void display(){
System.out.println("**GreenHeadDuck**");
}
}
/* RedHeadDuck.java */
public class RedHeadDuck extends Duck{
public RedHeadDuck() {
mFlyBehavior = new BadFlyBehavior(); //实例化为 BadFlyBehavior(不会飞)
}
@Override
public void display(){
System.out.println("**RedHeadDuck**");
}
}
(4)测试
/* StimulateDuck.java */
public class StimulateDuck {
public static void main(String[] args) {
Duck mGreenHeadDuck = new GreenHeadDuck(); //绿头鸭
Duck mRedHeadDuck = new RedHeadDuck(); //红头鸭
//绿头鸭出场
mGreenHeadDuck.display();
mGreenHeadDuck.quack();
mGreenHeadDuck.swim();
mGreenHeadDuck.fly();
//红头鸭出场
mRedHeadDuck.display();
mRedHeadDuck.quack();
mRedHeadDuck.swim();
mRedHeadDuck.fly();
//前面在构造函数中定义鸭子会不会飞,这里动态的将绿头鸭从 GoodFly 变成 BadFly
mGreenHeadDuck.display();
mGreenHeadDuck.SetFlyBehavior(new BadFlyBehavior());
mGreenHeadDuck.fly();
}
}
程序运行结果:
首先,绿头鸭和红头鸭都嘎嘎叫、游泳,但是绿头鸭会飞(GoodFly),红头鸭不会飞(BadFly)。
最后,我们动态的将绿头鸭从会飞设置成不会飞,所以绿头鸭输出了 BadFly 。
**GreenHeadDuck**
--gaga--
--swimming--
--GoodFly--
**RedHeadDuck**
--gaga--
--swimming--
--BadFly--
**GreenHeadDuck**
--BadFly--
这就是策略模式的解决思路。
通过封装行为族,超类不考虑具体的行为,而让子类去选择各自的行为。
这样,我们增加新的鸭子,超类的代码无需改动,子类也不需覆盖超类的代码,只需构造不同的实例即可实现子类的行为差异化。
3 小结
策略模式的核心是对算法族的封装,将变化和不变化的部分分离。其针对接口编程(代码复用),并通过组合代替继承(增加灵活性)。
假设我们从父类继承并重写特定的方法,来实现子类行为的差异化。这样在每次新增加的子类很可能都需要重写这些方法(代码复用率低),而且不够灵活(有些子类并不需要父类的方法)。
有了策略模式,我们将这些差异化的算法实现提取出来,封装成不同的算法实现类(实现自算法族接口)。通过父类来使用算法族接口,而子类只需实例化不同的算法实现类,即可实现子类行为的差异化。这样的好处是在实现子类差异化的同时,不需要修改父类的代码,在增加新特性时只需增加具体的算法实现类(并实现算法族接口),然后在子类中通过 Setter 设置不同的算法实现类。