1. 策略模式简介
策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变更和替换独立于使用算法的客户。
策略模式中体现了如下几种设计原则:
- 针对接口编程,而不是具体的实现类(定义了一个策略组接口)
- 使用组合/聚合的方式代替继承(StrategyContext类与策略组容器就是组合关系)
- 把变化的代码从不变的代码中分离出来
原理类图
Context:负责和具体的策略类交互;这样的话具体的算法和客户端调用分离了,使得算法可以独立于客户端独立的变化;
Strategy,B:策略类的抽象构建角色,定义规范或者策略类共有的部分(java8开始,接口可以定义default修饰,以及static修饰的方法);
ConcreteStrategyA,B,C,D:具体的策略实现;
2. 实例
本实例使用SSM框架实现。
2.1 Controller层
类中@AutoWriter注解标注的类StrategyContext strategyContext;就是上面类图中的Context;
接口compMonthlyData接收的参数之一String queryWay;来决定用户使用哪种算法;
package com.icex.bus.sys.controller;
import com.icex.frame.utils.DateUtil;
import com.icex.frame.utils.ReturnUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 投诉月度统计类
* @author Jjcc
* @version 1.0.0
* @description
* @className SysComplaintMonthlyController.java
* @createTime 2019年09月04日 09:46:00
*/
@Controller
@RequestMapping("/sys/comp/monthly")
public class SysComplaintMonthlyController {
@Autowired
StrategyContext strategyContext;
/**
* 投诉月度统计页面
* @title compMonthlyPage
* @description
* @author Jjcc
* @return java.lang.String
* @createTime 2019/9/5 10:05
* @throws
*/
@RequestMapping("compMonthlyPage")
public String compMonthlyPage() {
return "topJUI/sys/compDataStat/compMonthly/compMonthly_list";
}
/**
* 投诉月度统计获取数据
* @title compMonthlyData
* @description
* @author Jjcc
* @param queryYear 年份
* @param queryWay 根据单位,供电所...来统计数据
* @return java.lang.Object
* @createTime 2019/9/9 11:20
* @throws
*/
@RequestMapping("compMonthlyData.json")
@ResponseBody
public Object compMonthlyData(Integer queryYear, @RequestParam(required = false, defaultValue = "zrdw") String queryWay) {
Map<String, Object> map = new HashMap<>(8);
try {
if (null == queryYear) {
map = ReturnUtil.buildRetFailMap("查询年份不能为空", null);
return map;
}
if (StringUtils.isBlank(queryWay)) {
map = ReturnUtil.buildRetFailMap("queryWay不能为空", null);
return map;
}
int beginYear = queryYear - 1;
StringBuilder beginTime = new StringBuilder();
StringBuilder endTime = new StringBuilder();
beginTime.append(beginYear);
beginTime.append("-12-26 00:00:00");
endTime.append(queryYear);
endTime.append("-12-26 00:00:00");
//查询条件
Map<String, Object> condition = new HashMap<>(8);
condition.put("beginTime", DateUtil.getDateFromString(beginTime.toString(), "yyyy-MM-dd 00:00:00"));
condition.put("endTime", DateUtil.getDateFromString(endTime.toString(), "yyyy-MM-dd 00:00:00"));
List<HashMap<String, Object>> list = strategyContext.getCompMonthlyData(queryWay, condition);
map.put("list", list);
map.put("startTime", beginTime.toString());
map.put("endTime",endTime.toString());
map = ReturnUtil.buildRetSuccMap(map);
} catch (Exception e) {
map = ReturnUtil.buildRetErrMap(null);
e.printStackTrace();
}
return map;
}
}
2.2 StrategyContex
使用@Component将该类注册至IOC容器中;
关于@AutoWriter标注在Map容器中具体可以百度,这里不过多细说;
package com.icex.bus.sys.controller;
import com.icex.bus.enums.CompMonthlyTypeEnum;
import com.icex.bus.sys.service.ICompMonthlyStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 策略管理器
* @author Jjcc
* @version 1.0.0
* @description
* @className StrategyContext.java
* @createTime 2019年09月09日 14:23:00
*/
@Component
public class StrategyContext {
/**
* 注入ICompMonthlyStrategy子类的service,key为子类service的key
*/
@Autowired
private Map<String, ICompMonthlyStrategy> compMonthlyStrategy;
public List<HashMap<String, Object>> getCompMonthlyData(String queryWay, Map<String, Object> condition){
//调用枚举类中的getEnumByType方法通过参数queryWay获取对应的算法类信息
CompMonthlyTypeEnum enumByType = CompMonthlyTypeEnum.getEnumByType(queryWay);
//Spring容器初始化后,所有符合要求的Bean都会存入Map容器中,map的key即bean的name
ICompMonthlyStrategy iCompMonthlyStrategy = compMonthlyStrategy.get(enumByType.getService());
//调用算法族中共有的封装装算法的接口
List<HashMap<String, Object>> compMonthlyData = iCompMonthlyStrategy.getCompMonthlyData(condition);
return compMonthlyData;
}
}
2.3 CompMonthlyTypeEnum枚举类
枚举类中,定义了多个初始化枚举的方法,当你新增了策略类时,只需在枚举类中新增初始化方法即可;
这里使用枚举当工厂的优势是,枚举类是一个单例,并且天然的防止反射以及反序列化创建对象;
package com.icex.bus.enums;
/**
* compMonthly算法族枚举类,这里相当于工厂;
* @author Jjcc
* @version 1.0.0
* @description
* @className compMonthly.java
* @createTime 2019年09月09日 11:08:00
*/
public enum CompMonthlyTypeEnum {
/**
* 根据责任单位计算投诉月度统计
* @title
* @description
* @author Jjcc
* @return
* @createTime 2019/9/9 11:38
* @throws
*/
ZRDW("zrdw", "根据责任单位计算投诉月度统计", "compMonthlyUnitImpl"),
/**
* 根据供电所计算投诉月度统计
* @title
* @description
* @author Jjcc
* @return
* @createTime 2019/9/9 11:38
* @throws
*/
GDS("gds", "根据供电所计算投诉月度统计", "compMonthlyPsStationImpl"),
/**
* 根据各地市计算投诉月度统计
* @title
* @description
* @author Jjcc
* @return
* @createTime 2019/9/10 14:18
* @throws
*/
EACHCities("eachCities", "根据各地市计算投诉月度统计", "compMonthlyEachCitiesImpl"),
/**
* 根据考核班组计算投诉月度统计
* @title
* @description
* @author Jjcc
* @return
* @createTime 2019/9/10 14:18
* @throws
*/
KHbz("khbz", "根据考核班组计算投诉月度统计", "compMonthlyAssessTeamImpl"),
/**
* 根据施工队计算投诉月度统计
* @title
* @description
* @author Jjcc
* @return
* @createTime 2019/9/11 16:04
* @throws
*/
SGD("sgd", "根据施工队计算投诉月度统计", "compMonthlyConstructionTeamImpl"),
/**
* 根据线路计算投诉月度统计
* @title
* @description
* @author Jjcc
* @return
* @createTime 2019/9/11 16:15
* @throws
*/
XL("xl", "根据线路名称计算投诉月度统计", "compMonthlyCircuitNameImpl");
/**
* 类型
*/
private String queryWay;
/**
* 描述
*/
private String description;
/**
* service的BeanName
*/
private String service;
public String getQueryWay() {
return queryWay;
}
public String getDescription() {
return description;
}
public String getService() {
return service;
}
/**
* @title CompMonthlyTypeEnum
* @description
* @author Jjcc
* @param queryWay 客户调用何种算法的参数
* @param description 描述
* @param service 算法Bean的name
* @return
* @createTime 2019/9/19 15:24
* @throws
*/
CompMonthlyTypeEnum(String queryWay, String description, String service) {
this.queryWay = queryWay;
this.description = description;
this.service = service;
}
/**
* 根据参数queryWay获取需要调用的算法类信息
* @title getEnumByType
* @description
* @author Jjcc
* @param queryWay
* @return com.icex.bus.enums.CompMonthlyTypeEnum
* @createTime 2019/9/19 15:16
* @throws
*/
public static CompMonthlyTypeEnum getEnumByType(String queryWay) {
for (CompMonthlyTypeEnum value : CompMonthlyTypeEnum.values()) {
if (value.getQueryWay().equals(queryWay)) {
return value;
}
}
throw new RuntimeException("通过key获取枚举类异常");
}
}
2.4 算法族抽象构建类
请忽略本人写的逻辑代码,初始版,有待优化;
package com.icex.bus.sys.service;
import java.text.NumberFormat;
import java.util.*;
import java.util.stream.Stream;
/**
* 算法族抽象构建类
* @author Jjcc
* @version 1.0.0
* @description
* @className ICompMonthlyStrategy.java
* @createTime 2019年09月09日 10:30:00
*/
public interface ICompMonthlyStrategy {
/**
* 策略方法
* @title getCompMonthlyData
* @description
* @author Jjcc
* @param condition
* @return java.util.HashMap<java.lang.String,java.lang.Object>
* @createTime 2019/9/9 10:40
* @throws
*/
List<HashMap<String, Object>> getCompMonthlyData(Map<String, Object> condition);
/**
* 算法通用代码;
* 用于group by字段出现 “/” 的问题
* @title groupFieldMultiOne
* @description
* @author Jjcc
* @param list
* @return void
* @createTime 2019/9/9 17:33
* @throws
*/
default void groupFieldMultiOne(List<HashMap<String, Object>> list){
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
list.stream().filter(p -> {
String str = String.valueOf(p.get("group_field_two"));
if (str.contains("/")) {
return true;
}
return false;
}).forEach(c -> {
//责任单位字段含有2个或2个以上部门
String dutyUnit = String.valueOf(c.get("group_field_two"));
String[] dutyUnits = dutyUnit.split("/");
for (int i = 0; i<dutyUnits.length; i++) {
if("检修公司配电室".equals(dutyUnits[i])) {
dutyUnits[i] = "配电检修公司";
} else if("检修公司电缆室".equals(dutyUnits[i])) {
dutyUnits[i] = "电缆检修公司";
} else if("检修公司输电室".equals(dutyUnits[i])) {
dutyUnits[i] = "输电检修公司";
} else if("检修公司检修室".equals(dutyUnits[i]) || "检修公司运维室".equals(dutyUnits[i])) {
dutyUnits[i] = "变电检修公司";
}
}
Set<Map.Entry<String, Object>> set = new HashSet<>();
c.entrySet().forEach(cMap -> {
//将含有多个部门的不为空的数据存入Set
if (!"zrdw".equals(String.valueOf(cMap.getKey())) && !"group_field_two".equals(String.valueOf(cMap.getKey())) && !"0".equals(String.valueOf(cMap.getValue()))) {
System.out.println("mSet:" +cMap);
set.add(cMap);
}
});
Stream.of(dutyUnits).forEach(m -> {
list.stream().filter(pTarget -> {
String deptName = String.valueOf(pTarget.get("group_field_one"));
return "null".equals(deptName) ? false : true ;
}).forEach(listMap -> {
String dutyUnitName = String.valueOf(listMap.get("group_field_one"));
if (m.equals(dutyUnitName)) {
set.forEach(mSet -> {
double s1 = Double.parseDouble((listMap.get(mSet.getKey())).toString());
double s2 = Double.parseDouble(mSet.getValue().toString()) / dutyUnits.length;
listMap.put(mSet.getKey(), nf.format(s1 + s2));
});
}
});
});
});
//根据总计字段降序排序
list.sort(Comparator.comparingDouble((t) -> -Double.parseDouble(t.get("counts").toString())));
}
/**
* 算法通用代码-考核班组,施工队,线路;
* 用于group by字段出现 “/” 的问题
* @title groupFieldMultiOne
* @description
* @author Jjcc
* @param list
* @return void
* @createTime 2019/9/9 17:33
* @throws
*/
default void groupFieldMultiTwo(List<HashMap<String, Object>> list) {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
List<HashMap<String, Object>> newAddList = new ArrayList<>();
HashMap<String, Object> clone = (HashMap<String, Object>)list.get(0).clone();
for (Map.Entry<String, Object> stringObjectEntry : clone.entrySet()) {
stringObjectEntry.setValue("0");
}
list.stream().filter(p -> {
String str = String.valueOf(p.get("group_field_two"));
if (str.contains("/")) {
return true;
}
return false;
}).forEach(c -> {
//字段含有2个或2个以上部门
String dutyUnit = String.valueOf(c.get("group_field_two"));
String[] dutyUnits = dutyUnit.split("/");
Set<Map.Entry<String, Object>> set = new HashSet<>();
c.entrySet().forEach(cMap -> {
//将含有多个部门的不为空的数据存入Set
if (!"group_field_two".equals(String.valueOf(cMap.getKey())) && !"0".equals(String.valueOf(cMap.getValue()))) {
set.add(cMap);
}
});
Stream.of(dutyUnits).forEach(m -> {
//字段中多个部门中的某一个部门在list中没有找到的话,需要在list中新增
boolean boole = list.stream().filter(pTarget -> {
String deptName = String.valueOf(pTarget.get("group_field_one"));
return !"null".equals(deptName) ? true : false;
}).anyMatch(listMap -> {
String dutyUnitName = String.valueOf(listMap.get("group_field_one"));
if (m.equals(dutyUnitName)) {
//多部门中的某一个部门存在于list集合中,进行相应的数据调整;
set.forEach(mSet -> {
double s1 = Double.parseDouble((listMap.get(mSet.getKey())).toString());
double s2 = Double.parseDouble(mSet.getValue().toString()) / dutyUnits.length;
listMap.put(mSet.getKey(), nf.format(s1 + s2));
});
return true;
}
return false;
});
if (!boole) {
//多部门中某一个部门于list中不存在,则在集合中新增一个map
HashMap<String, Object> map = (HashMap<String, Object>)clone.clone();
map.put("group_field_one",m);
map.put("group_field_two",m);
set.forEach(mSet -> {
double s1 = Double.parseDouble(mSet.getValue().toString()) / dutyUnits.length;
map.put(mSet.getKey(), nf.format(s1));
});
newAddList.add(map);
}
});
});
list.addAll(newAddList);
//根据总计字段降序排序
list.sort(Comparator.comparingDouble((t) -> -Double.parseDouble(t.get("counts").toString())));
}
}
2.5 算法族具体的构建类
当需要增加新的策略时,只需新增一个ICompMonthlyStrategy 接口的实现类即可;
package com.icex.bus.sys.service.impl;
import com.icex.bus.sys.dao.SysComplaintDao;
import com.icex.bus.sys.service.ICompMonthlyStrategy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
/**
* ICompMonthlyStrategy类具体的构建类-根据线路统计
* @author Jjcc
* @version 1.0.0
* @description
* @className CompMonthlyUnitImpl.java
* @createTime 2019年09月09日 10:43:00
*/
@Service("compMonthlyCircuitNameImpl")
public class CompMonthlyCircuitNameImpl implements ICompMonthlyStrategy {
@Resource
private SysComplaintDao sysComplaintDao;
/**
* 根据线路统计方法
* @title getCompMonthlyData
* @description
* @author Jjcc
* @param condition
* @return java.util.List<java.util.HashMap<java.lang.String,java.lang.Object>>
* @createTime 2019/9/9 11:05
* @throws
*/
@Override
public List<HashMap<String, Object>> getCompMonthlyData(Map<String, Object> condition) {
List<HashMap<String, Object>> list = sysComplaintDao.selectMonthlyCompCircuitNameTj(condition);
if (null != list && list.size() > 0) {
//如果集合为空,则表示统计的数据没有,则不需要进行后续操作
//用于字段中存在多个部门算法调用
groupFieldMultiTwo(list);
HashMap<String, Object> totalList = sysComplaintDao.selectMonthlyCompTotalCircuitNameTj(condition);
list.add(totalList);
}
return list;
}
}
总结
在Spring项目中,使用策略模式。
定义一个算法族接口,实现不同的具体构建类,并用@Service("...")注册。
定义一个Context类,类中定义一个成员变量的集合;Map<String, 算法族接口类型>,并用@Autowired标记,Spring源码中@AutoWired注释大意为:当该注解标记于Map,List等集合时,会查找容器中所有注册的Bean并放入标记的集合中(通过集合的类型查找,Map的value为查找的类型;ps:如果是Map集合,查找到符合条件的Bean后,Bean的name是key,Bean本身是value);
可以通过工厂模式,枚举,反射等获得客户所需要的算法所在的Bean;
end!!!