Java SSM项目运用策略模式思想

1. 策略模式简介

策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变更和替换独立于使用算法的客户。

策略模式中体现了如下几种设计原则:

  1. 针对接口编程,而不是具体的实现类(定义了一个策略组接口)
  2. 使用组合/聚合的方式代替继承(StrategyContext类与策略组容器就是组合关系)
  3. 把变化的代码从不变的代码中分离出来

原理类图

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!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值