需求:对于用户操作统一处理/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);
}
第四步:新增功能
例如当前想要新增评论举报
- 新增枚举
COMMENT_REPORT(8,"report_count","评论举报")
- 新增策略类
@Component("COMMENT_REPORT")
public class CommentReportStrategy implements UserActionStrategy {
@Override
public void execute(UserAction userAction) {
// 举报逻辑
}
}
Service 一行代码不用改
而这种写法符合两个设计原则:
- 开闭原则(对扩展开放,对修改关闭):新增行为只新增类,不修改原代码
- 单一职责(每个策略只负责一种行为)
最终项目结构:
action
├─ strategy
│ ├─ UserActionStrategy
│ │
│ ├─ comment
│ │ ├─ CommentLikeStrategy
│ │ ├─ CommentHateStrategy
│ │ └─ CommentReportStrategy
│ │
│ └─ video
│ ├─ VideoLikeStrategy
│ ├─ VideoCollectStrategy
│ └─ VideoCoinStrategy

评论(0)
暂无评论