业务开发过程中,有的业务具有相似性,初级的做法可能是对每一种业务都copy一份代码,然后在代码里做微调;
这样会造成一个结果:如果后续逻辑有变化,那么左右的方法都要修改,有的分支没有改完咋办
另一个结果就是:工程里到处都是重复代码,让人眼花缭乱
高级的做法:
这里我们可以用策略模式来处理这种情况,策略模式你如果会并理解了,那么你对面向对象的:封装、继承和多态也就理解了;
业务背景
- 学校有很多社团,社团里有3种管理员:社长,副社长、团支书;
- 每个社团都有自己的一个分类,分类和社团是1对多的关系
业务任务
- 我们需要统计:全部社长、全部副社长、全部团支书的人员信息
- 我们需要按照不同的社团类型来统计所有管理员的人员信息
设计分析
社团每天都在新增,管理员的数量也会随着变化;我们可以用定时任务来进行每天的增量数据统计
代码设计
- 控制类(入口类或者叫做客户端类)
- 算法抽象类,封装共有逻辑
- 两个子策略类,实现专有逻辑
控制类拥有所有的策略类,根据场景决定使用哪一个策略类;子策略类可以根据业务发展自定义扩展
类图设计:
控制类
思路:控制类(入口类或者叫做客户端类)需要拥有所有策略类的实现,通过策略匹配调用具体策略类做逻辑处理;
这个类的核心价值在于能够 根据参数决定去执行哪一个策略,这也就是策略模式的 核心;
package com.xx.job.societygroup;
import com.mass.peking.api.teacher.entity.SocietyGroup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author EDY
*/
@Component
@Slf4j
public class GroupMemberJobContext {
@Resource
private AbstractSocietyGroupHandler[] handlers;
/**
* 统计入口
* @param group 分组
*/
public void staticGroupMember(SocietyGroup group) {
for (AbstractSocietyGroupHandler handler : handlers) {
// 策略问询
if (handler.support(group)) {
log.info("自动同步分组:{} 组员", group.getName());
handler.parse(group);
log.info("自动同步分组:{} 组员结束", group.getName());
break;
}
}
}
}
抽象统计类
该类封装统计的共性逻辑,各业务特有的逻辑定义成抽象方法让子类去实现
package com.xx.job.societygroup;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mass.peking.api.community.entity.OrgSocietyInfo;
import com.mass.peking.api.teacher.entity.SocietyGroup;
import com.mass.peking.api.teacher.entity.SocietyGroupMember;
import com.mass.peking.api.teacher.service.ISocietyGroupMemberService;
import com.mass.peking.common.util.Constants;
import com.mass.peking.common.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author EDY
*/
@Slf4j
public abstract class AbstractSocietyGroupHandler {
/**
* societyGroupMemberService
*/
@Resource
protected ISocietyGroupMemberService societyGroupMemberService;
/**
* 是否支持该组
*
* @param group 分组名称
* @return bool
*/
protected abstract boolean support(SocietyGroup group);
/**
* 构建 OrgSocietyGroupMembers 列表
*
* @param group 分组名称
* @return 分组与人员关系列表对象
*/
protected abstract List<SocietyGroupMember> buildOrgSocietyGroupMembers(SocietyGroup group);
/**
* 解析分组
* <p>
* 不使用批量删除后再批量写入,这样会造成主键id每天成倍递增
*
* @param group 分组名称
*/
@Transactional(rollbackFor = Exception.class)
public void parse(SocietyGroup group) {
//需要删除的数据
List<SocietyGroupMember> deletes = new ArrayList<>();
// 分组所有的成员信息
List<SocietyGroupMember> members = this.buildOrgSocietyGroupMembers(group);
List<SocietyGroupMember> membersCopy = new ArrayList<>(members);
// 分页查询数据库里的数据,避免内存溢出
long current = 1;
List<SocietyGroupMember> dbPageList;
do {
Page<SocietyGroupMember> page = new Page<>(current, 200);
QueryWrapper<SocietyGroupMember> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("group_id", group.getId());
Page<SocietyGroupMember> ipage = societyGroupMemberService.page(page, queryWrapper);
dbPageList = ipage.getRecords();
// 解析需要删掉和需要插入的数据
this.removeInDB(membersCopy, dbPageList);
deletes.addAll(this.parseDelete(members, dbPageList));
// 翻页
current++;
} while (dbPageList.size() > 0);
if (!CollectionUtils.isEmpty(membersCopy)) {
// 批量写入
this.societyGroupMemberService.saveBatch(membersCopy);
log.info("定时解析分组:{} 批量写入数据:{}", group.getName(), members.size());
}
if (!CollectionUtils.isEmpty(deletes)) {
log.info("需要删除的集合:{}", deletes.size());
QueryWrapper<SocietyGroupMember> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id", deletes.stream().map(SocietyGroupMember::getId).collect(Collectors.toList()));
this.societyGroupMemberService.remove(queryWrapper);
}
}
/**
* 从全集中去掉数据库中已经存在的数据
*
* @param members 数据全集
* @param inDB 数据库里一页数据
*/
private void removeInDB(List<SocietyGroupMember> members, List<SocietyGroupMember> inDB) {
this.removeIf(members, inDB);
}
private void removeIf(List<SocietyGroupMember> all, List<SocietyGroupMember> sub) {
all.removeIf(m -> sub.stream().filter(db -> db.getSocietyId() != null && db.getGroupId() != null && db.getUserId() != null).anyMatch(db -> db.getSocietyId().equals(m.getSocietyId()) && db.getGroupId().equals(m.getGroupId()) && db.getUserId().equals(m.getUserId())));
}
/**
* 获取数据库中存在但是在全集中不存在的数据
*
* @param members 数据全集
* @param inDB 数据库中的分页数据
* @return 需要删掉的数据
*/
private List<SocietyGroupMember> parseDelete(List<SocietyGroupMember> members, List<SocietyGroupMember> inDB) {
List<SocietyGroupMember> inDBCopy = new ArrayList<>(inDB);
this.removeIf(inDBCopy, members);
return inDBCopy;
}
/**
* 组装SocietyGroupMember对象
*
* @param list OrgSocietyInfo 对象
* @param managerType 管理员类型
* @param group 分组信息
* @return List
*/
protected List<SocietyGroupMember> parseSocietyGroupMember(List<OrgSocietyInfo> list, String managerType, SocietyGroup group) {
return list.stream().map(info -> {
SocietyGroupMember member = new SocietyGroupMember();
member.setSocietyId(info.getSocietyId());
member.setGroupId(group.getId());
switch (managerType) {
case Constants.MANAGER_TYPE_PRESIDENT:
// 社长
member.setUserId(info.getPresidentId());
break;
case Constants.MANAGER_TYPE_DIRECTOR:
// 理事长
member.setUserId(info.getDirectorId());
break;
case Constants.MANAGER_TYPE_SECRETARY:
// 团支书
member.setUserId(info.getSecretaryId());
break;
default:
log.error("异常:>>>>>>>>>>>>>>>>>>>>>>>>>>{}", managerType);
break;
}
member.setCreatedAt(DateUtils.getIntDate());
return member;
}).collect(Collectors.toList());
}
}
子策略类1
所有管理员:会长、副会长、团支书统计策略子类
package com.xx.job.societygroup;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mass.peking.api.community.entity.OrgSocietyInfo;
import com.mass.peking.api.society.service.OrgSocietyInfoService;
import com.mass.peking.api.teacher.entity.SocietyGroup;
import com.mass.peking.api.teacher.entity.SocietyGroupMember;
import com.mass.peking.common.util.Constants;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 社长分组、副会长分组、团支书分组 处理器
*
* @author EDY
*/
@Component
public class SocietyInfoManagerGroupHandler extends AbstractSocietyGroupHandler {
private List<String> supports = Arrays.asList("社长分组", "副会长分组", "团支书分组");
@Resource
private OrgSocietyInfoService orgSocietyInfoService;
@Override
public boolean support(SocietyGroup group) {
return supports.contains(group.getName());
}
@Override
public List<SocietyGroupMember> buildOrgSocietyGroupMembers(SocietyGroup group) {
// 查询所有的社长
QueryWrapper<OrgSocietyInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("president_id", 0);
queryWrapper.select("society_id", "president_id");
List<OrgSocietyInfo> list = orgSocietyInfoService.list(queryWrapper);
List<SocietyGroupMember> res = new ArrayList<>(this.parseSocietyGroupMember(list, Constants.MANAGER_TYPE_PRESIDENT, group));
// 查询所有的理事长
queryWrapper = new QueryWrapper<>();
queryWrapper.gt("director_id", 0);
queryWrapper.select("society_id", "director_id");
list = orgSocietyInfoService.list(queryWrapper);
res.addAll(this.parseSocietyGroupMember(list, Constants.MANAGER_TYPE_DIRECTOR, group));
// 查询所有的团支书
queryWrapper = new QueryWrapper<>();
queryWrapper.gt("secretary_id", 0);
queryWrapper.select("society_id", "secretary_id");
list = orgSocietyInfoService.list(queryWrapper);
res.addAll(this.parseSocietyGroupMember(list, Constants.MANAGER_TYPE_SECRETARY, group));
// return
return res;
}
}
子策略类2
指定类型社团的管理员统计策略
package com.xx.job.societygroup;
import com.mass.peking.api.community.entity.OrgSocietyInfo;
import com.mass.peking.api.community.entity.OrgSocietyType;
import com.mass.peking.api.community.service.OrgSocietyTypeService;
import com.mass.peking.api.society.service.OrgSocietyInfoService;
import com.mass.peking.api.teacher.entity.SocietyGroup;
import com.mass.peking.api.teacher.entity.SocietyGroupMember;
import com.mass.peking.common.util.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
* 普通分组处理逻辑
*
* @author EDY
*/
@Component
@Slf4j
public class CommonGroupHandler extends AbstractSocietyGroupHandler {
// 社员分组名称与社团类型名称对应关系
private static final HashMap<String/*社员分组名称*/, String/*社团类型名称*/> MAP = new HashMap() {{
put("政治理论类管理员分组", "政治理论类");
put("学术科创类管理员分组", "学术科创类");
put("文化艺术类管理员分组", "文化艺术类");
put("体育健身类管理员分组", "体育健身类");
put("公益志愿类管理员分组", "公益志愿类");
put("实践促进类管理员分组", "实践促进类");
put("合作交流类管理员分组", "合作交流类");
put("地域文化类管理员分组", "地域文化类");
put("心理辅导类管理员分组", "心理辅导类");
put("组织机构管理员分组", "组织机构管理类");
}};
@Resource
private OrgSocietyTypeService orgSocietyTypeService;
/**
* 社团详细信息服务
*/
@Resource
private OrgSocietyInfoService orgSocietyInfoService;
@Override
public boolean support(SocietyGroup group) {
return MAP.containsKey(group.getName());
}
@Override
public List<SocietyGroupMember> buildOrgSocietyGroupMembers(SocietyGroup group) {
// 分组名称
String groupName = group.getName();
// 获得需要统计的社团类型名称
String type = MAP.get(groupName);
if (type == null) {
log.warn("未找到分组:{} 对应的社团类型", groupName);
return Collections.emptyList();
}
log.info("开始统计分组:{} 对应的社团类型:{}", groupName, type);
// 根据社团类型每次查询
OrgSocietyType societyType = this.orgSocietyTypeService.getByTypeName(type);
if (societyType == null) {
log.warn("未找到分组:{} 对应的社团类型", groupName);
return Collections.emptyList();
}
// 查询管理者
List<OrgSocietyInfo> societyInfoList = this.orgSocietyInfoService.getOrgSocietyInfoBySocietyType(societyType.getId());
// 社长
List<OrgSocietyInfo> presidentList = societyInfoList.stream().filter(i -> i.getPresidentId() != null && i.getPresidentId() > 0).collect(Collectors.toList());
// 理事长
List<OrgSocietyInfo> directorList = societyInfoList.stream().filter(i -> i.getDirectorId() != null && i.getDirectorId() > 0).collect(Collectors.toList());
// 团支书
List<OrgSocietyInfo> secretaryList = societyInfoList.stream().filter(i -> i.getSecretaryId() != null && i.getSecretaryId() > 0).collect(Collectors.toList());
// 组装对象
List<SocietyGroupMember> societyGroupMembers = this.parseSocietyGroupMember(presidentList, Constants.MANAGER_TYPE_PRESIDENT, group);
societyGroupMembers.addAll(this.parseSocietyGroupMember(directorList, Constants.MANAGER_TYPE_DIRECTOR, group));
societyGroupMembers.addAll(this.parseSocietyGroupMember(secretaryList, Constants.MANAGER_TYPE_SECRETARY, group));
log.info("分组:{}, 对应的社团类型:{}, 成员数量:{}", groupName, type, societyGroupMembers.size());
return societyGroupMembers;
}
}
两个策略都继承了公共的策略,只是查询源数据的方法各自走各自的逻辑;这样达到了代码的复用,符合开闭原则;
封装: 抽象类封装了公共的算法
继承: 子类策略通过继承公共算法类,而且实现自己的专有逻辑
多态: 在 GroupMemberJobContext类的handlers属性,我们通过父类的引用把所有子类都注入进来,真正执行方法的时候是子类对象在真实执行
再回顾一下类图,你就很清晰了:
over~~