设计模式 - 策略模式

前言

各位帅哥美女编码的时候有没有下面这种情况?

或者是

繁杂的条件,几十上百种情况是不是让人上火?
这个时候就可以考虑使用策略模式简化上面这两种情况了。

策略模式

定义

策略模式是指有一定行动内容的相对稳定的策略名称。策略模式在古代中又称“计策”,简称“计”,如《汉书·高帝纪上》:“汉王从其计”。这里的“计”指的就是计谋、策略。策略模式具有相对稳定的形式,如“避实就虚”、“出奇制胜”等。一定的策略模式,既可应用于战略决策,也可应用于战术决策;既可实施于大系统的全局性行动,也可实施于大系统的局部性行动。(内容来自百度百科)

个人理解

策略模式重点是如何组织调度算法,而不在于编写算法。
策略模式下各种算法是平等的相互没有依赖的可替换的。
一次抉择只可以使用一种策略。

适用场景

场景其实就是我上面两张图,业务需要根据不同场景切换不同的实现逻辑时
1.替换大量冗长的ifelse
2.替换大量的switch

策略模式的优点

1、策略模式其实就是提供管理相关的算法集的办法。策略类可以封装一种算法或行为,那么当某个策略发生变化的时候,在修改代码时,其他策略不受影响。在新增策略的时候不用修改已有代码,符合开闭原则。
2、使用策略模式可以避免较多的ifelse和switch。

策略模式的缺点

1.客户端必须知道所有的策略,并且自己决定使用哪种策略。客户端需要了解对应的策略。
2.当策略很多的时候,类的数量就会很多。

具体案例

在对我自己的按理进行讲解的时候,我会不断的升级使用方式,来达到策略模式的最佳使用方式。
案例是我自己想的可能稍有些奇葩,但是最终目的是为了自己和看文章的同学能理解到位了,如有冒犯,请多担待
2019年年末,北京发现了外地来的鼠疫患者,我只是看到了消息,还好没什么状况。然后就和家人讨论:如果疫情真的来了,家里要做什么准备,然后每个人说自己的看法。
我哥:
1.我肯定要先买口罩
2.坐最快的火车直接回老家
3.回去以后有地里种的小麦和蔬菜养着肯定饿不死
我姐:
1.我也先买口罩
2.然后去最近的超市采购主食,要囤至少三个月的主食在家里
3.然后买一些能放置时间长的蔬菜,再买一些平时吃的东西,
我:
1.我打算什么都不做

根据上面的疫情来了后我们三个商量出的三种办法做三个策略类
首先定义一个接口
下面展示一些 内联代码片

public interface IStrategy {
	void doSomething();
}

然后根据商量好的策略编写策略类

public class GoHomeStrategy implements IStrategy{
	@Override
	public void doSomething() {
		System.out.println("买口罩");
		System.out.println("坐车回家");
		System.out.println("种地务农");
	}
}
public class GoHomeStrategy implements IStrategy{
	@Override
	public void doSomething() {
		System.out.println("买口罩");
		System.out.println("坐车回家");
		System.out.println("种地务农");
	}
}
public class WaitDeathStrategy implements IStrategy{
	@Override
	public void doSomething() {
		System.out.println("什么都不做");
	}
}

使用方式

直接使用创建策略类对象

封装好的策略类,并不一定是为了客户端同学使用的,也可以是为了我们自己使用的。也不一定是用参数控制,策略类可以直接具象化到类对象的创建上。并不完全符合开闭原则,但是却也是最小的范围的修改,代价很小。

public class SwitchDemo {
	public static void main(String[] args) {
		IStrategy iStrategy = new WaitDeathStrategy();
		iStrategy.doSomething();
	}
}

执行结果如下

我们也可以换个策略,执行结果如下

可以将策略类在项目启动时初始化,将策略key与策略类绑定,使用时,直接使用策略key

public class SwitchDemo {
	public static void main(String[] args) {
		Map<String, IStrategy> map = new HashMap<>();
		map.put("gohome", new GoHomeStrategy());
		map.put("goshopping", new GoShoppingStrategy());
		map.put("waitdeath", new WaitDeathStrategy());

		IStrategy iStrategy = map.get("gohome");
		iStrategy.doSomething();
	}
}

执行结果如下

通常我们不想让策略的使用者与策略关联太紧密,因此初始化策略应该对使用者屏蔽,那我们就来创建一个策略上下文来持有所有的策略

public class StrategyContext {
	private static Map<String, IStrategy> map = new HashMap<>();
	static {
		map.put("gohome", new GoHomeStrategy());
		map.put("goshopping", new GoShoppingStrategy());
		map.put("waitdeath", new WaitDeathStrategy());
	}

	public void doSomeThing(String key) {
		map.get(key).doSomething();
	}
}
public class SwitchDemo {
	public static void main(String[] args) {
		StrategyContext strategyContext = new StrategyContext();
		strategyContext.doSomeThing("gohome");
	}
}

执行结果如下

可以使用map+lamda函数,但是前提是策略需要做的事情不大复杂(我需要换个例子进行举例,并且我认为可能这种情况的应用并不多)

public class Calculator {
    private static Map<String, BiFunction<Integer,Integer,Integer>> operatorMap = new HashMap<>();
    static{
        operatorMap.put("+",(a,b) -> a+b);
        operatorMap.put("-",(a,b) -> a-b);
        operatorMap.put("*",(a,b) -> a*b);
        operatorMap.put("/",(a,b) -> a/b);
    }
    public int calculate(int left,String operator,int right){
        return operatorMap.get(operator)!=null?operatorMap.get(operator).apply(left,right):-1;
    }
}

如果你的策略实现不是单独的类例如如下这样

public class StrategyImpl{
	public void doSomething1() {
		System.out.println("买口罩");
		System.out.println("坐车回家");
		System.out.println("种地务农");
	}
	public void doSomething2() {
		System.out.println("买口罩");
		System.out.println("买主食");
		System.out.println("买蔬菜");
	}
	public void doSomething3() {
		System.out.println("什么都不做");
	}
}

那么你的策略类可以改写成这样

public class StrategyContext1 {
	private static Map<String, Function<String, String>> map = new HashMap<>();
	private static StrategyImpl strategy;
	static {
		map.put("gohome",(arg1)->strategy.doSomething1());
		map.put("goshopping", (arg1)->strategy.doSomething2());
		map.put("waitdeath", (arg1)->strategy.doSomething3());
	}
	public void doSomeThing(String key) {
		map.get(key).apply("");
	}
}

我认为到底是否要使用这种关键是看你的策略是否要单独存放
//todo:持续增加中 开源项目中的策略模式讲解

参考文章

1.策略模式的理解
2.由浅入深理解策略模式