一、定义
策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。
二、问题
假设有一款枪战游戏,玩家们想要切换不同的武器去击杀敌人,首先武器有手枪和步枪,游戏上线不久后又想添加坦克,那么应该怎么做才能使得加入坦克而不影响整个类呢?
三、解决方案
策略模式建议找出负责用许多不同方式完成特定任务的类, 然后将其中的算法抽取到一组被称为策略的独立类中。
名为上下文的原始类(环境类)必须包含一个成员变量来存储对于每种策略的引用。 上下文并不执行任务, 而是将工作委派给已连接的策略对象。
上下文不负责选择符合任务需要的算法——客户端会将所需策略传递给上下文。 实际上, 上下文并不十分了解策略, 它会通过同样的通用接口与所有策略进行交互, 而该接口只需暴露一个方法来触发所选策略中封装的算法即可。
因此, 上下文可独立于具体策略。 这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了。
四、实现
1、策略接口
接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。
package com.atmae.strategy;
/**
* @Author: Mae
* @Date: 2022/3/23
* @Time: 13:57
* @Description:
*/
public interface ArmsStrategy {
/**
* 策略接口的抽象方法
*/
void equipArms();
}
2、上下文原始类
维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。
package com.atmae.strategy;
/**
* @Author: Mae
* @Date: 2022/3/23
* @Time: 13:55
* @Description: 环境上下文
*/
public class ArmsContext {
private ArmsStrategy armsStrategy;
public ArmsContext(ArmsStrategy armsStrategy) {
this.armsStrategy = armsStrategy;
}
public void getArms() {
this.armsStrategy.equipArms();
}
}
3、具体策略类
实现了上下文所用算法的各种不同变体。
手枪策略
package com.atmae.strategy;
/**
* @Author: Mae
* @Date: 2022/3/23
* @Time: 14:00
* @Description:
*/
public class HandgunStrategy implements ArmsStrategy {
@Override
public void equipArms() {
System.out.println("已经切换为手枪");
}
}
步枪策略
package com.atmae.strategy;
/**
* @Author: Mae
* @Date: 2022/3/23
* @Time: 14:01
* @Description:
*/
public class RifleStrategy implements ArmsStrategy {
@Override
public void equipArms() {
System.out.println("已经切换为步枪");
}
}
坦克策略
package com.atmae.strategy;
/**
* @Author: Mae
* @Date: 2022/3/23
* @Time: 14:17
* @Description:
*/
public class TankStrategy implements ArmsStrategy{
@Override
public void equipArms() {
System.out.println("已经切换为坦克");
}
}
4、客户端
会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。
package com.atmae.strategy;
import java.util.Scanner;
/**
* @Author: Mae
* @Date: 2022/3/23
* @Time: 14:54
* @Description:
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
try {
ArmsContext armsContext = (ArmsContext) Class.forName("com.atmae.strategy." + s).newInstance();
armsContext.getArms();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
5、使用
五、UML图
六、策略模式适合应用场景
-
当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。
策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。 -
当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。
策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。 -
如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。 -
当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。
策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。
七、总结
优点
- 你可以在运行时切换对象内的算法。
- 你可以将算法的实现和使用算法的代码隔离开来。
- 你可以使用组合来代替继承。
- 开闭原则。 你无需对上下文进行修改就能够引入新的策略。
缺点
- 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
- 客户端必须知晓策略间的不同——它需要选择合适的策略。