boxmoe_header_banner_img

Hello! 欢迎来到我的博客!

加载中

文章导读

[设计模式]使用策略工厂模式优化代码


avatar
xiaoifei 2026年7月2日 6

需求:对于用户操作统一处理/userAction/doAction,比如(视频点赞,视频收藏,视频投币,评论点赞,评论点踩…)
使用策略工厂模式可以保证对修改关闭,对新增开放的接口开闭原则

待优化代码

@Service
public class UserActionServiceImpl implements UserActionService {
    @Autowired
    private UserActionMapper<UserAction, UserActionQuery> userActionMapper;
    @Autowired
    private VideoInfoMapper<VideoInfo, VideoInfoQuery> videoInfoMapper;
    @Autowired
    private UserInfoMapper<UserInfo, UserInfoQuery> userInfoMapper;

    /**
     * 根据条件查询列表
     */
    @Override
    public List<UserAction> findListByParam(UserActionQuery param) {
        return this.userActionMapper.selectList(param);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveAction(UserAction userAction) {
        String videoId = userAction.getVideoId();
        VideoInfo videoInfo = videoInfoMapper.selectByVideoId(videoId);
        if(videoInfo == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        userAction.setVideoUserId(videoInfo.getUserId());
        UserActionTypeEnum userActionTypeEnum = UserActionTypeEnum.getByType(userAction.getActionType());
        if(userActionTypeEnum == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }

        // 交互:有则取消,无则交互
        UserAction dbAction = userActionMapper.selectByVideoIdAndCommentIdAndActionTypeAndUserId(videoId, userAction.getCommentId(), userAction.getActionType(), userAction.getUserId());
        userAction.setActionTime(new Date());

        switch (userActionTypeEnum) {
            case VIDEO_LIKE:
            case VIDEO_COLLECT:
                if(dbAction == null) {
                    userActionMapper.insert(userAction);
                } else {
                    userActionMapper.deleteByActionId(dbAction.getActionId());
                }
                Integer changeCount = dbAction == null ? 1 : -1;
                videoInfoMapper.updateCountInfo(videoId,  userActionTypeEnum.getField(), changeCount);

                // TODO ES更新
                if (userActionTypeEnum == UserActionTypeEnum.VIDEO_COLLECT) {
                    //更新es收藏数量
                }
                break;
            case VIDEO_COIN:
                if(dbAction != null) {
                    throw new BusinessException(MessageConstant.ALREADY_COIN);
                }
                if(userAction.getVideoUserId().equals(userAction.getUserId())) {
                    throw new BusinessException(MessageConstant.CANT_COIN_SELF);
                }
                userInfoMapper.selectByUserId(userAction.getUserId());
                // 扣币
                Integer result = userInfoMapper.updateCoinCountInfo(userAction.getUserId(), -userAction.getActionCount());
                if(result == 0) {
                    throw new BusinessException(MessageConstant.INSUFFICIENT_COINS);
                }
                // 加币
                Integer result2 = userInfoMapper.updateCoinCountInfo(userAction.getVideoUserId(), userAction.getActionCount());
                if(result2 == 0) {
                    throw new BusinessException(MessageConstant.COINS_FAILED);
                }

                userActionMapper.insert(userAction);
                videoInfoMapper.updateCountInfo(videoId, userActionTypeEnum.getField(), userAction.getActionCount());
                break;
        }
    }
}
public enum UserActionTypeEnum {

    COMMENT_LIKE(0, "like_count", "评论喜欢点赞"),
    COMMENT_HATE(1, "hate_count", "评论讨厌"),
    VIDEO_LIKE(2, "like_count", "视频点赞"),
    VIDEO_COLLECT(3, "collect_count", "视频收藏"),
    VIDEO_COIN(4, "coin_count", "视频投币"),
    VIDEO_COMMENT(5, "comment_count", "视频评论数"),
    VIDEO_DANMU(6, "danmu_count", "弹幕评论数"),
    VIDEO_PLAY(7, "play_count", "视频播放数");


    private Integer type;
    private String field;
    private String desc;

    UserActionTypeEnum(Integer type, String field, String desc) {
        this.type = type;
        this.field = field;
        this.desc = desc;
    }

    public static UserActionTypeEnum getByType(Integer type) {
        for (UserActionTypeEnum item : UserActionTypeEnum.values()) {
            if (item.getType().equals(type)) {
                return item;
            }
        }
        return null;
    }

    public Integer getType() {
        return type;
    }

    public String getDesc() {
        return desc;
    }

    public String getField() {
        return field;
    }
}

这种代码如何利用设计模式进行拆解呢

行为策略模式

UserActionStrategy  
	 |  
┌────┴─────┐  
LikeActionStrategy  
CollectActionStrategy  
CoinActionStrategy

改造重点

  • switch 替换为 UserActionStrategy 的实现集合,每种行为单独一个策略类负责自己的业务逻辑。
  • UserActionService 中根据 actionType 查找策略并执行,保持 service 层职责清晰(校验、路由、日志/事务)。
  • 保持事务在 service 层(@Transactional),策略内部不单独管理事务(除非需要新事务)。
  • 考虑并发(DB 原子更新 / 乐观锁 / Redis + 异步刷盘),以及索引与幂等性。
// 策略接口  
public interface UserActionStrategy {  
	UserActionTypeEnum getType();  
	void execute(UserAction userAction);  
}
@Service
public class UserActionServiceImpl implements UserActionService {
    private final Map<UserActionTypeEnum, UserActionStrategy> strategyMap;
    private final VideoInfoMapper<VideoInfo, VideoInfoQuery> videoInfoMapper;

    @Autowired
    public UserActionServiceImpl(List<UserActionStrategy> strategies,
                                 VideoInfoMapper<VideoInfo, VideoInfoQuery> videoInfoMapper,
                                 /* other mappers */) {
        this.videoInfoMapper = videoInfoMapper;
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(UserActionStrategy::getType, Function.identity()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveAction(UserAction userAction) {
        // 基本校验
        VideoInfo videoInfo = videoInfoMapper.selectByVideoId(userAction.getVideoId());
        if (videoInfo == null) throw new BusinessException(ResponseCodeEnum.CODE_600);
        userAction.setVideoUserId(videoInfo.getUserId());
        UserActionTypeEnum actionType = UserActionTypeEnum.getByType(userAction.getActionType());
        if (actionType == null) throw new BusinessException(ResponseCodeEnum.CODE_600);
        userAction.setActionTime(new Date());

        // 找到策略并执行
        UserActionStrategy strategy = strategyMap.get(actionType);
        if (strategy == null) throw new BusinessException(ResponseCodeEnum.CODE_600);
        strategy.execute(userAction);
    }
}

点赞策略

@Component
public class VideoLikeStrategy implements UserActionStrategy {

    @Autowired
    private UserActionMapper userActionMapper;
    @Autowired
    private VideoInfoMapper videoInfoMapper;

    @Override
    public UserActionTypeEnum getType() {
        return UserActionTypeEnum.VIDEO_LIKE;
    }

    @Override
    public void execute(UserAction userAction) {
        UserAction db = userActionMapper.selectByVideoIdAndCommentIdAndActionTypeAndUserId(
            userAction.getVideoId(), userAction.getCommentId(), userAction.getActionType(), userAction.getUserId()
        );

        if (db == null) {
            userActionMapper.insert(userAction);
            videoInfoMapper.updateCountInfo(userAction.getVideoId(), getType().getField(), 1);
        } else {
            userActionMapper.deleteByActionId(db.getActionId());
            videoInfoMapper.updateCountInfo(userAction.getVideoId(), getType().getField(), -1);
        }
    }
}

投币策略

@Component
public class VideoCoinStrategy implements UserActionStrategy {

    @Autowired private UserActionMapper userActionMapper;
    @Autowired private UserInfoMapper userInfoMapper;
    @Autowired private VideoInfoMapper videoInfoMapper;

    @Override
    public UserActionTypeEnum getType() {
        return UserActionTypeEnum.VIDEO_COIN;
    }

    @Override
    public void execute(UserAction userAction) {
        // 幂等检查:已投则抛错
        UserAction db = userActionMapper.selectByVideoIdAndCommentIdAndActionTypeAndUserId(
            userAction.getVideoId(), userAction.getCommentId(), userAction.getActionType(), userAction.getUserId()
        );
        if (db != null) throw new BusinessException(MessageConstant.ALREADY_COIN);

        if (userAction.getVideoUserId().equals(userAction.getUserId())) {
            throw new BusinessException(MessageConstant.CANT_COIN_SELF);
        }

        // 扣币(依赖 mapper 返回受影响行数做并发保护)
        int deducted = userInfoMapper.updateCoinCountInfo(userAction.getUserId(), -userAction.getActionCount());
        if (deducted == 0) throw new BusinessException(MessageConstant.INSUFFICIENT_COINS);

        int added = userInfoMapper.updateCoinCountInfo(userAction.getVideoUserId(), userAction.getActionCount());
        if (added == 0) throw new BusinessException(MessageConstant.COINS_FAILED);

        userActionMapper.insert(userAction);
        videoInfoMapper.updateCountInfo(userAction.getVideoId(), getType().getField(), userAction.getActionCount());
    }
}

上面 saveAction 在 service 层加了 @Transactional,因此整个流程在一个事务内执行,保证原子性。

使用的时候不需要修改UserActionServiceImpl,只需要新建雷继承策略接口实现关键方法即可,因为Spring会自动管理注入bean

策略模式优化

刚才那种写法是:

List<UserActionStrategy> → 手动转 Map

其实在 Spring 里还有一种更优雅的方式,这种方式不需要自己维护 Map。即用 Spring 容器本身当作策略容器。 让actionType = BeanName,这样就可以 直接从 Spring 容器取策略

第一步:给策略指定 BeanName

// 评论点赞
@Component("COMMENT_LIKE")
public class CommentLikeStrategy implements UserActionStrategy {

    @Override
    public void execute(UserAction userAction) {
        // 评论点赞逻辑
    }
}
// 评论点踩
@Component("COMMENT_HATE")
public class CommentHateStrategy implements UserActionStrategy {

    @Override
    public void execute(UserAction userAction) {
        // 评论点踩逻辑
    }
}
// 视频点赞
@Component("VIDEO_LIKE")
public class VideoLikeStrategy implements UserActionStrategy {

    @Override
    public void execute(UserAction userAction) {
        // 视频点赞
    }
}

第二步:在 Service 里直接拿 Map

Spring 会自动生成:

@Autowired
private Map<String, UserActionStrategy> strategyMap;

Spring 注入后的内容:

{
 COMMENT_LIKE -> CommentLikeStrategy
 COMMENT_HATE -> CommentHateStrategy
 VIDEO_LIKE   -> VideoLikeStrategy
 VIDEO_COIN   -> VideoCoinStrategy
}

key 就是 @Component("xxx") 里的名字。

第三步:根据 actionType 找策略

@Transactional
public void saveAction(UserAction userAction) {

    UserActionTypeEnum typeEnum =
            UserActionTypeEnum.getByType(userAction.getActionType());

    if (typeEnum == null) {
        throw new BusinessException(ResponseCodeEnum.CODE_600);
    }

    UserActionStrategy strategy =
            strategyMap.get(typeEnum.name());

    if (strategy == null) {
        throw new BusinessException("不支持的操作类型");
    }

    strategy.execute(userAction);
}

第四步:新增功能

例如当前想要新增评论举报

  1. 新增枚举
COMMENT_REPORT(8,"report_count","评论举报")
  1. 新增策略类
@Component("COMMENT_REPORT")
public class CommentReportStrategy implements UserActionStrategy {

    @Override
    public void execute(UserAction userAction) {
        // 举报逻辑
    }
}

Service 一行代码不用改
而这种写法符合两个设计原则:

  1. 开闭原则(对扩展开放,对修改关闭):新增行为只新增类,不修改原代码
  2. 单一职责(每个策略只负责一种行为)

最终项目结构:

action
 ├─ strategy
 │   ├─ UserActionStrategy
 │   │
 │   ├─ comment
 │   │     ├─ CommentLikeStrategy
 │   │     ├─ CommentHateStrategy
 │   │     └─ CommentReportStrategy
 │   │
 │   └─ video
 │         ├─ VideoLikeStrategy
 │         ├─ VideoCollectStrategy
 │         └─ VideoCoinStrategy


评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码