代码设计 面向对象封装、继承、多态全撸之策略模式

业务开发过程中,有的业务具有相似性,初级的做法可能是对每一种业务都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~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值