为什么要学设计模式?
因为设计模式是面向对象的具体实现,你将学习其他开发人员的经验,解决在特定场景下的问题,设计模式在开发过程中随处可见,它真的很重要!!!
开始学习策略模式…
1.1 引言
在开发过程中,当遇到如下场景:
- 一款游戏,不同的鸭子具有不同的飞行行为,例如,一般的鸭子具有飞行的行为,橡皮鸭和模型鸭不具有飞行的行为。
- 购物时,不同会员等级对应的商品价格折扣是不同的,折扣具有不同的算法,例如,金牌会员打7折,银牌会员打8折;
就上述场景而言,针对某一问题,具有不同的应对策略(行为或算法等,统称策略)。
鸭子的飞行策略可能根据需求而变化,例如,添加一种火箭鸭,其飞行由火箭动力驱动。
如果不对这些变化的策略进行抽象和封装,可能引起代码无法复用、可维护性差和可扩展性差等问题。
在这种场景下,可以应用策略模式,来解决上述问题。
1.2 利用继承和接口添加鸭子飞行行为
一款关于鸭子的游戏,鸭子Duck是抽象类,它有很多不同类型鸭子的实现。
现在需要为鸭子加入飞行的功能,即fly(),并且不同的鸭子具有不同的飞行行为。
一、利用继承的方法
设计方案如类图1所示:
类图1.利用继承为鸭子添加飞行行为
Duck是抽象父类,其具有飞行行为fly(),但其子类RubberDuck不具有飞行行为,
因此,在子类RubberDuck()中,重写fly(),覆盖父类的fly()方法,使其成为一个空方法,不具有飞行行为。
如果新增一只诱饵鸭DecoyDuck,也要重写父类的fly()方法,使其不具有飞行行为,
这使得fly()空方法在子类无法复用,每增加一个不具有飞行行为的鸭子,子类均要重写fly()空方法。
试想,如果子类的fly()空方法,具有大量代码,这就会使得更多的代码无法复用。
二、利用接口的方法
设计方案如类图2所示:
类图2.利用接口为鸭子添加飞行行为
把fly()从超类中取出来,放进一个“Flyable接口”中。这么一来,只有会飞的鸭子才实现此接口。
根据不同的鸭子,实现自己的飞行行为。
但这样也使得,每增加一类具有飞行行为的鸭子时,其飞行行为也无法复用,例如,绿头鸭和红头鸭的飞行行为是相同的,但代码无法复用。
1.3 利用策略模式添加鸭子飞行行为
设计方案如类图3所示:
类图3.利用策略模式添加鸭子行为.puml
鸭子的飞行行为是变化的部分,因此,将飞行行为封装起来,通过Duck与FlyBehavior的组合,实现鸭子的飞行行为与其他代码解耦;以后若有新的飞行行为,可通过实现FlyBehavior接口添加飞行行为即可,这具有较好的扩展性,也有利于维护代码。
通过策略模式实现的代码如下:
Duck类:
package headfirst.designpatterns.strategy;
public abstract class Duck {
FlyBehavior flyBehavior;
public Duck() {
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
abstract void display();
public void performFly(){
flyBehavior.fly();
}
}
MallardDuck类:
package headfirst.designpatterns.strategy;
public class MallardDuck extends Duck{
@Override
void display() {
System.out.println("I'm a real Mallard duck");
}
}
ModelDuck类:
package headfirst.designpatterns.strategy;
public class ModelDuck extends Duck{
@Override
void display() {
System.out.println("I'm a model duck");
}
}
RubberDuck类:
package headfirst.designpatterns.strategy;
public class RubberDuck extends Duck{
@Override
void display() {
System.out.println("I'm a rubber duck");
}
}
FlyBehavior 接口: 封装飞行类
package headfirst.designpatterns.strategy;
public interface FlyBehavior {
void fly();
}
FlyWithWings 类:具体的飞行行为-可以飞
package headfirst.designpatterns.strategy;
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying!!");
}
}
FlyNoWay 类:具体的飞行行为-不能飞
package headfirst.designpatterns.strategy;
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can't fly");
}
}
FlyRocketPowered类:新增一种火箭驱动的飞行行为,利用策略模式的可扩展性好,也便于维护。
package headfirst.designpatterns.strategy;
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying with a rocket");
}
}
StrategyPatternDemo 测试类:
package headfirst.designpatterns.strategy;
/**
* @author: sjmp1573
* @date: 2022/4/29 10:59
* @description:
*/
public class StrategyPatternDemo {
public static void main(String[] args) {
FlyBehavior flyNoWay = new FlyNoWay();
Duck modelDuck = new ModelDuck();
// 为模型鸭注入飞行行为,即无法飞行;
modelDuck.setFlyBehavior(flyNoWay);
modelDuck.display();
modelDuck.performFly();
System.out.println("------------");
//运行时,动态改变模型鸭的飞行行为,利用火箭驱动飞行
FlyRocketPowered flyRocketPowered = new FlyRocketPowered();
modelDuck.setFlyBehavior(flyRocketPowered);
modelDuck.display();
modelDuck.performFly();
System.out.println("------------");
// 创建一只橡皮鸭,复用方法
Duck rubberDuck = new RubberDuck();
rubberDuck.setFlyBehavior(flyNoWay);
rubberDuck.display();
rubberDuck.performFly();
}
}
输出:
I'm a model duck
I can't fly
------------
I'm a model duck
I'm flying with a rocket
------------
I'm a rubber duck
I can't fly
Process finished with exit code 0
结果分析:模型鸭刚开始不具有飞行行为,通过策略模式,在运行时可改变模型鸭的飞行行为,即火箭驱动。并且橡皮鸭也不具有飞行行为,复用了 FlyBehavior 接口下的 FlyNoWay 飞行行为。
以前鸭子类继承超类或直接实现接口,重写行为方法,子类依靠实现。现在通过策略模式,实现了面向接口编程。
1.4 总结
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
好处在于:代码复用、扩展灵活、可维护性好。
OO原则:
- 封装变化,(封装鸭子的飞行行为)
- 多用组合,少用继承,(Duck与FlyBehavior的组合,而不是使用类或接口的继承)
- 针对接口编程,不针对实现编程,(飞行的具体行为在FlyBehavior接口中实现)
GitHub 代码地址:https://github.com/SJMP1573/DesignPatterns.git
参考
[1] Freeman E. Head First 设计模式[M] 中国电力出版社.
[2] 策略模式 | 菜鸟教程.