设计模式-策略模式
前言
大家好,今天带大家学习设计模式中使用最频繁也是最简单的一种模式 “策略模式”,那先说一下为什么要用设计模式,或者换句话什么场景要用策略模式。当然,大家要注意,这只是一种编程规范,并不是一种规定,一定要分清规范和规定的区别,这样说就是要告诉大家不要因为用设计模式而用设计模式,给大家举个简单的例子。
比如你拿100元去超市买了一瓶哇哈哈(5元),这时候收银员需要找给你95元,他可以选择找给你一张50元,两张20元,一张5元,很显然这是非常好的一种策略,节省双方时间。但是收银员却偏偏找给你 95张1元的,恐怕这时候你心里会问候他全家了。但就事实来说,这两种找钱策略都没有问题,因为你收到了95元,这种行为虽然不违法,但是你心里肯定觉得这家伙有毛病。
通过这个例子,大家可以看出,一般正常人都会选择第一种找钱的策略,这可没有人去规定必须这样做,大家默认的一种共识,就可以理解为某个层面的“规范”。
带入到我们程序中也是这样的,一个功能 有N中实现方式,但如果你偏要选择一种繁琐、难以维护的方式去做,那么后期难受的不止你一个人,还有后期替你维护的人,这就是大部分公司都会有一堆“屎山”的原因。
正文
假设现在有这样一个需求要求你去做,公司内用户都有一个等级分别是LEVEL_0 ~ LEVEL_3,每月月初(1号)用户在登陆后会根据等级不同而赠送不同的积分和金币。
规则如下:
LEVEL_0 ⇒ 什么也不赠送
LEVEL_1 ⇒ 20积分
LEVEL_2 ⇒ 30积分+10金币
LEVEL_3 ⇒ 50积分+30金币
当你接收到这样一个需求时,你会怎样去做?
先给大家写个伪代码
//存储用户信息
Map<String,String> userInfo = new HashMap<String, String>(){
{
put("id","u001");
put("name","张三");
put("level","LEVEL_3");
}
};
//获取用户等级
String level = userInfo.get("level");
//判断用户属于哪个等级,从而做出不同的操作
if("LEVEL_0".equals(level)){
System.out.println("什么也不赠送");
}else if("LEVEL_1".equals(level)){
System.out.println("赠送20积分");
}else if("LEVEL_2".equals(level)){
System.out.println("赠送30积分");
System.out.println("赠送10金币");
}else if("LEVEL_3".equals(level)){
System.out.println("赠送50积分");
System.out.println("赠送30金币");
}
这种是不是很多的写法,当然不可否认,这样写也可以完美解决这个需求,并且就从目前来看,代码量也很少,一眼就能看清楚。
但请大家思考一下,一般一个大型的项目是由多人协同开发的,可能这个迭代周期你负责维护,下一个周期是别人在维护,当需求变更时,你就会在这对if else中加入更多的代码以及更多的if else if else,或者switch。
在迭代的过程中,慢慢的没人愿意去动这块的代码,因为风险很大,每次变更需求后需要进行一次全量测试。因为你改变了这块代码,但这块代码中有N中可能,你必须保证每种可能执行的结果都能满足预期。
经过n次迭代后,你的代码结构能如下:
//存储用户信息
Map<String,String> userInfo = new HashMap<String, String>(){
{
put("id","u001");
put("name","张三");
put("level","LEVEL_3");
}
};
//获取用户等级
String level = userInfo.get("level");
if(xx){
//dosometing
if(xx){
//dosomething
}
}else{
//dosomething
}
//判断用户属于哪个等级,从而做出不同的操作
if("LEVEL_0".equals(level)){
if(xx){
//dosomething
}
System.out.println("什么也不赠送");
}else if("LEVEL_1".equals(level)){
if(xx){
//dosomething
}else{
System.out.println("赠送20积分");
}
}else if("LEVEL_2".equals(level)){
if(xx){
//dosometing
System.out.println("赠送30积分");
}else{
System.out.println("赠送20积分");
}
//dosomething
}else if("LEVEL_3".equals(level)){
System.out.println("赠送50积分");
System.out.println("赠送30金币");
}
你会发现,你的代码逐渐往失控的方向走去,没人愿意看到这样一坨一坨的代码,因为他的结构并不清晰,单元测试量也会提升,导致每次修改一点代码,都需要对所有可能进行回归验证。
如果你不想看到这样的事情发生,那就接着往下看吧。
为了避免一些字面量的if else,我们首先将他们定义到枚举中,因为枚举类会让你的代码变的没那么容易出现简单的错误,比如 你将
if(“LEVEL_3”.equals(level))
错误的拼写为
if("LEEVL_3".equals(level))
可以很大程度避免这些低级错误,当然他的好处不止这些,能让后期维护你代码的人一眼能看出允许哪些值的出现。
首先,我们先定义原有两个业务接口,用户积分service和用户金币service,其中各自有一个方法,分别为用户增加积分和金币。
//用户积分service
public class UserScoreService {
/**
* 向用户增加N个积分
* @param userName
* @param score
* @return
*/
public boolean addUserScore(String userName,long score){
System.out.println(String.format("为用户%s增加%s个积分",userName,score));
return true;
}
}
//用户金币service
public class UserGoldService {
/**
* 向用户增加N个金币
* @param userName
* @param goldNumber
* @return
*/
public boolean addUserGold(String userName,long goldNumber){
System.out.println(String.format("为用户%s增加%s个金币",userName,goldNumber));
return true;
}
}
接下我,我们先定义一个枚举类,如下:
public enum UserLevelEnum {
LEVEL_0,
LEVEL_1,
LEVEL_2,
LEVEL_3;
private static Map<String,UserLevelEnum> KV_CACHE = new HashMap<String, UserLevelEnum>(){
{
for (UserLevelEnum level : UserLevelEnum.values()) {
put(level.name(),level);
}
}
};
public static UserLevelEnum getByName(String name){
return KV_CACHE.get(name);
}
}
该枚举类中包含了LEVEL_0~LEVEL_1的枚举项。
有了枚举类之后,我们该定义一个接口
public interface UserScoreProcessor {
UserLevelEnum support() throws Exception;
void process(Map<String,String> userInfo) throws Exception;
Integer order();
}
- UserLevelEnum support() 用来返回一个当前处理器可以处理的类型
- void process(Map<String,String> userInfo) 入参为用户信息实体,用于处理具体业务
- Integer order() 处理器的排序,按照升序去执行
我们将接口定义好之后,按照道理应该去实现对吧?但是为了以后容易扩展,建议大家做一个适配层,也就是 适配器模式的一种实现,具体如下。
public abstract class UserScoreProcessorAdapter implements UserScoreProcessor {
private static Integer DEFAULT_ORDER = 0;
private UserScoreService userScoreService;
private UserGoldService userGoldService;
public UserScoreProcessorAdapter(){
this.userGoldService = new UserGoldService();
this.userScoreService = new UserScoreService();
}
@Override
public UserLevelEnum support() throws Exception {
throw new Exception("此处理器暂不支持");
}
@Override
public void process(Map<String, String> userInfo) throws Exception {
}
@Override
public Integer order() {
return DEFAULT_ORDER;
}
protected boolean addUserScore(String userName,long score){
return userScoreService.addUserScore(userName,score);
}
protected boolean addUserGold(String userName,long goldNumber){
return userGoldService.addUserGold(userName,goldNumber);
}
}
可以看到,我们定义了一个抽象类,去实现了刚定义的接口,并将方法进行了默认实现,将 ScoreService和 GoldService聚合到了抽象类中。
接下来,我们该创建具体的实现类,来实现功能业务了。
可以看到,这里创建了4个实现类,分别去处理LEVEL_0~LEVEL_1的业务。
public class Level0Processor extends UserScoreProcessorAdapter {
@Override
public UserLevelEnum support() throws Exception {
return UserLevelEnum.LEVEL_0;
}
@Override
public void process(Map<String, String> userInfo) throws Exception {
String name = userInfo.get("name");
System.out.println("当前用户为LEVEL_0 ,不做任何操作");
}
}
public class Level1Processor extends UserScoreProcessorAdapter {
@Override
public UserLevelEnum support() throws Exception {
return UserLevelEnum.LEVEL_1;
}
@Override
public void process(Map<String, String> userInfo) throws Exception {
String name = userInfo.get("name");
addUserScore(name,20);
}
}
public class Level2Processor extends UserScoreProcessorAdapter {
@Override
public UserLevelEnum support() throws Exception {
return UserLevelEnum.LEVEL_2;
}
@Override
public void process(Map<String, String> userInfo) throws Exception {
String name = userInfo.get("name");
addUserScore(name,30);
addUserGold(name,10);
}
}
public class Level3Processor extends UserScoreProcessorAdapter {
@Override
public UserLevelEnum support() throws Exception {
return UserLevelEnum.LEVEL_3;
}
@Override
public void process(Map<String, String> userInfo) throws Exception {
String name = userInfo.get("name");
addUserScore(name,50);
addUserGold(name,30);
}
}
可以看到,我们在每个实现类中都去重写了 process() 方法,针对每个等级,做了不同的业务处理。
到现在为止,我们的工作已经完成大部分了,接下来我们该去思考,如何让他们按照用户等级不同去调用对应的实现类。
首先,我们应该有一个类,这个类中应该包含等级和具体实现的映射,能够根据不同的用户等级去调用我们事先定义好的处理类,有了思路,就开始实现吧。
public class UserScoreProcessorFactory extends UserScoreProcessorAdapter{
/**
* 简易单例
*/
public static UserScoreProcessorFactory INSTANCE = new UserScoreProcessorFactory();
/**
* 保存 用户等级 与 具体处理类的实现
*/
private static Map<UserLevelEnum, List<UserScoreProcessor>> processors = new HashMap<>(16);
static {
try {
/**
* 将我们实现的处理类注入到 工厂类中
*/
register(new Level0Processor());
register(new Level1Processor());
register(new Level2Processor());
register(new Level3Processor());
/**
* 对处理类进行排序
*/
processors.values().forEach(e -> {
e.sort((o1,o2) -> o1.order()-o2.order());
});
}catch (Throwable t){
t.printStackTrace();
}
}
private UserScoreProcessorFactory(){
}
/**
* 将处理类注册到工厂类中(也可以说是策略上下文)
* @param processor
* @return
* @throws Exception
*/
public static UserScoreProcessor register(UserScoreProcessor processor) throws Exception {
UserLevelEnum support = processor.support();
List<UserScoreProcessor> userScoreProcessors = processors.get(support);
if(userScoreProcessors==null){
List<UserScoreProcessor> list = new ArrayList<UserScoreProcessor>();
processors.put(support,list);
userScoreProcessors = list;
}
if(!userScoreProcessors.contains(processor)){
userScoreProcessors.add(processor);
}
return processor;
}
@Override
public void process(Map<String, String> userInfo) throws Exception {
UserLevelEnum level = UserLevelEnum.getByName(userInfo.get("level"));
List<UserScoreProcessor> userScoreProcessors = processors.get(level);
if(userScoreProcessors == null || userScoreProcessors.isEmpty()){
throw new Exception("没有支持此类型的处理器");
}
executeProcessor(level,userInfo);
}
private void executeProcessor(UserLevelEnum level,Map<String, String> userInfo) throws Exception {
List<UserScoreProcessor> userScoreProcessors = processors.get(level);
for (UserScoreProcessor userScoreProcessor : userScoreProcessors) {
userScoreProcessor.process(userInfo);
}
}
}
可以看到,在我们工厂类中,我们定义一个Map去存储用户等级与具体处理器的映射,在静态代码块中,我们将所有处理器注册进了map中。
process()方法中,我们先获取用户的等级,然后获取其对应的枚举值,通过map获取到对应的处理器集合,最终循环去调用对应的处理器。
到这里我们就完成了整个业务功能的开发。
运行一下试试吧。
public static void main(String[] args) throws Exception {
UserScoreProcessorFactory factory = UserScoreProcessorFactory.INSTANCE;
Map<String,String> userInfo = new HashMap<String, String>(){
{
put("id","u001");
}
};
userInfo.put("name","李四");
userInfo.put("level","LEVEL_0");
//LEVEL_0
factory.process(userInfo);
userInfo.put("name","王五");
userInfo.put("level","LEVEL_1");
//LEVEL_1
factory.process(userInfo);
userInfo.put("name","赵六");
userInfo.put("level","LEVEL_2");
//LEVEL_2
factory.process(userInfo);
userInfo.put("name","钱七");
userInfo.put("level","LEVEL_3");
//LEVEL_3
factory.process(userInfo);
}
执行结果:
当前用户为LEVEL_0 ,不做任何操作
为用户王五增加20个积分
为用户赵六增加30个积分
为用户赵六增加10个金币
为用户钱七增加50个积分
为用户钱七增加30个金币
后言:
大家在学习设计模式时,一定要注意,他只是一种规范,并没有规定一定要按照哪种结构去写。同时也不要为了用设计模式而去学设计模式,不要硬套设计模式,那么会产生反向的效果。
如果喜欢的话点个关注吧,有任何问题可以私信我,有问必答。