策略模式 - 白话详解以及和工厂模式的区别
前言
第一眼看到策略模式(Strategy Pattern)这四个大字的时候
我可太喜欢它的名字了
策略?对我这个喜欢看三国的人来说,这两个字简直在闪闪发光
又说它主要解决在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护的问题
这么优雅的吗?!
我要学这个!
奔现
看了看它的通用实现
先创建一个抽象策略接口(妙啊👏)
public interface IStrategy {
void doSomething();
}
然后创建实现策略接口的具体策略(理应如此)
public class StrategyA implements IStrategy {
@Override
public void doSomething() {
System.out.println("策略A");
}
}
public class StrategyB implements IStrategy {
@Override
public void doSomething() {
System.out.println("策略B");
}
}
再创建封装类(这如此简单的 class 要干啥用,真是高深啊)
public class Context {
private IStrategy strategy;
public Context(IStrategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.doSomething();
}
}
最后调用(额)
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new StrategyA());
context.executeStrategy();
context = new Context(new StrategyB());
context.executeStrategy();
}
}
啊这
这不就是OOP里的继承 + 多态么
而且,实际开发怎么用啊?
- 那么多实现了 IStrategy 的对象,开发的时候不能写死具体用哪几个,得动态选吧
- 动态选,那 Context.class 得和工厂模式配合用
- 用工厂模式,那还是需要参数和 if...else 逻辑来判断创建哪个策略类
啊这
真的鸡肋,吗
“因为喜欢,所以都对”,我尝试思索出策略模式的优点
(看到了吧JYM,名字起的好真的很重要啊)
贴合开发的优点:利于代码复用
举个例子:实现一个简单计算器,让它能对 a、b 两个数做加减
我们先定义一个接口
public interface IStrategy {
public int doOperation(int a, int b);
}
再定义加减法 class 并实现 IStrategy
public class Add implements IStrategy {
@Override
public int doOperation(int a, int b) {
return a + b;
}
}
public class Subtract implements IStrategy {
@Override
public int doOperation(int a, int b) {
return a - b;
}
}
然后程序运行时要根据用户输入,动态的选择“加”或“减”
假如不用策略模式,那么最直接的写法就是用一堆 if...else + 参数来选择
public class SimpleCalculator {
public static void main(String[] args) {
String mockUserInput = "Add";
IStrategy strategy = null;
if ("Add".equals(mockUserInput)) {
strategy = new Add();
} else if ("Subtract".equals(mockUserInput)) {
strategy = new Subtract();
}
System.out.println(strategy.doOperation(3, 2));
}
}
运行输出3 + 2 = 5
挺好
有一天又来个需求,要实现个电脑,这电脑里也有个简单算数的功能
这不是巧了吗
有现成的简单计算器的代码
这时不同人就有不同的做法了,最常见的2种:
- 把 SimpleCalculator 里的 if...else 直接复制过去(屎山的来源之一)
- 把 SimpleCalculator 里的 if...else 抽出一个方法,然后在 SimpleComputer 里调用
public class SimpleCalculator {
public static void main(String[] args) {
String mockUserInput = "Add";
IStrategy strategy = getStrategy(mockUserInput);
System.out.println(strategy.doOperation(3, 2));
}
// 抽出一个方法
public static IStrategy getStrategy(String mockUserInput) {
IStrategy strategy = null;
if ("Add".equals(mockUserInput)) {
strategy = new Add();
} else if ("Subtract".equals(mockUserInput)) {
strategy = new Subtract();
}
return strategy;
}
}
public class SimpleComputer {
public static void main(String[] args) {
// 调用
IStrategy strategy = SimpleCalculator.getStrategy("Subtract");
System.out.println(strategy.doOperation(3, 2));
}
}
抽出一个方法然后调用的做法比直接复制好太多
但是吧
SimpleCalculator 和 SimpleComputer 本身没有一毛钱关系,这样调用不别扭么?
别扭就对了
用专业术语说,这种写法违反了设计模式的原则:
- 违反单一职责原则:从角色上讲,那堆 if...else 是算法本身,而 SimpleCalculator 是算法的使用者,它们不应该混在一起
- 违反迪米特法则:SimpleComputer 只是想用下算法,和 SimpleCalculator 没有任何其它交集,最好不要这么调用
想象一下
为了实现这个电脑的简单计算功能,却把整个计算器都焊到了电脑上,能用是能用,但这不太合适吧?
那如果新建个 class 封装并对外提供这堆 if...else 算法逻辑呢(想象成把计算逻辑烧到单片机里)
public class ChipContext {
private IStrategy strategy;
public ChipContext(String mockUserInput) {
if ("Add".equals(mockUserInput)) {
this.strategy = new Add();
} else if ("Subtract".equals(mockUserInput)) {
this.strategy = new Subtract();
}
}
}
光封装起来没用,主要目的还是供人使用
那就再加个doOperation()
吧
/*
* 只是为了举例,实际开发别这么写
* 实际开发时,个人认为一般情况下 ChipContext、Add、Subtract 使用单例模式足以
*/
public class ChipContext {
private IStrategy strategy;
public ChipContext(String mockUserInput) {
IStrategy strategy = null;
if ("Add".equals(mockUserInput)) {
this.strategy = new Add();
} else if ("Subtract".equals(mockUserInput)) {
this.strategy = new Subtract();
}
}
public int doOperation(int a, int b) {
return this.strategy.doOperation(a, b);
}
}
眼熟不?
这就是配合了工厂模式的策略模式呀
而且 SimpleCalculator、SimpleComputer 可以这么用了
public class SimpleCalculator {
public static void main(String[] args) {
String mockUserInput = "Add";
ChipContext context = new ChipContext(mockUserInput);
System.out.println(context.doOperation(3, 2));
}
}
public class SimpleComputer {
public static void main(String[] args) {
ChipContext context = new ChipContext("Subtract");
System.out.println(context.doOperation(3, 2));
}
}
优雅不
与工厂模式的区别
回忆下上面的例子
在上例中,我们的关注点是实现加减功能,而不是计算器和电脑
对吧?
这就是策略模式和工厂模式的区别
工厂模式关注的是对象的创建:好比想要一台电脑、想要一台计算器,工厂给你生产出来
策略模式关注的是行为的封装:好比要开发一台电脑或者计算器,你想实现加减法。是 a+b 还是 b+a,由你决定;是 a×10÷10+b 还是 (a+b),也由你决定。对外暴露的就是加减功能,用户能知道有这俩功能就行
总结
设计模式只是把 if...else 提到更高的地方了,最终还是逃不过逻辑上的 if...else
策略模式当然也是设计模式的一种(虽然可以用枚举类或者Map消除明面上的 if...else,但内部实现上还是),它本质上就是OOP里的继承 + 多态,且常与工厂模式配合使用,目前觉得利于代码复用这一优点就很好,至于其它优缺点,拗口的答案就不在此赘述了
我觉得新人在开发时用不用设计模式不重要,重要的是要有一颗想写好代码的心。在实现功能的基础上,能做到代码的整洁、重构、复用就完全OK。日积月累,总会灵光一现,会开始用设计模式来比照代码,此所谓厚积薄发
感谢阅读~不喜勿喷。欢迎讨论、指正
转载自:https://juejin.cn/post/7217993597617946684