转载

以选秀节目为例替换代码中的if else

前言

起源是看了一篇文章 在 Spring Boot 中,如何干掉 if else! ,有兴趣的可以看下。感觉工作中也经常处理类似的问题,就整理了一下。

选秀节目只是当作一个场景,并不会讲太多,如果你真的有兴趣也可以联系我(认真脸。

选手分组

背景

选秀节目事前会让参赛者填一份资料,除了基本信息,还有你的定位,比如主唱、主舞、rap,节目组需要对参赛选手进行一个简单的分组。

实现

定义一个参赛选手类

@Data
public class User {

    private Long id;

    private String name;

    private int age;

    private Position position;
}

Position是个枚举

public enum Position {
    /**
     * 主唱
     */
    VOCAL,
    /**
     * 主舞
     */
    DANCE,
    /**
     * RAP
     */
    RAP
}

定义一个分组的接口

public interface IGroupService {

    void group(User user);
    
}

最直接的实现方式就是if else或者switch

@Slf4j
@Service
public class GroupServiceImpl implements IGroupService {
    @Override
    public void group(User user) {
        if (user.getPosition() == Position.VOCAL) {
            log.info("{}({})划分到主唱组", user.getName(), user.getId());
        }
        if (user.getPosition() == Position.DANCE) {
            log.info("{}({})划分到主舞组", user.getName(), user.getId());
        }
        if (user.getPosition() == Position.RAP) {
            log.info("{}({})划分到RAP组", user.getName(), user.getId());
        }
    }
}

优化

可以定义一个handler

public interface IGroupHandler {

    void group(User user);

    /**
     * 能处理的定位
     *
     * @return
     */
    Position getPosition();
}

再写三个实现

@Slf4j
@Component
public class VocalGroupHandler implements IGroupHandler {
    @Override
    public void group(User user) {
        log.info("{}({})划分到主唱组", user.getName(), user.getId());
    }

    @Override
    public Position getPosition() {
        return Position.VOCAL;
    }
}
@Slf4j
@Component
public class DanceGroupHandler implements IGroupHandler {
    @Override
    public void group(User user) {
        log.info("{}({})划分到主舞组", user.getName(), user.getId());
    }

    @Override
    public Position getPosition() {
        return Position.DANCE;
    }
}
@Slf4j
@Component
public class RapGroupHandler implements IGroupHandler {
    @Override
    public void group(User user) {
        log.info("{}({})划分到RAP组", user.getName(), user.getId());
    }

    @Override
    public Position getPosition() {
        return Position.RAP;
    }
}

然后在服务类里注入这些handler,在处理的时候通过每个handler能处理的类型,找到具体的handler

@Service
public class GroupServiceV2Impl implements IGroupService {
    public Map<Position, IGroupHandler> handlerMap;

    public GroupServiceV2Impl(List<IGroupHandler> groupHandlers) {
        handlerMap = groupHandlers.stream().collect(Collectors.toMap(IGroupHandler::getPosition, Function.identity()));
    }

    @Override
    public void group(User user) {
        IGroupHandler groupHandler = handlerMap.get(user.getPosition());
        if (groupHandler == null) {
            throw new IllegalArgumentException("无法处理的position:" + user.getPosition());
        }
        groupHandler.group(user);
    }
}

这里利用了spring的依赖注入,通过构造器注入把所有的handler进行注入。

后续如果Position有变更,只需要新增或修改相应的handler类即可。

选手评级

背景

选秀节目最开始会对选手评级,A、B、C、D、E、F几个等级。

导师们也都有各自擅长的领域,比如评价分为几个方面,唱跳rap,没有篮球。

可以假定一个评价规则

等级 评价规则
A 3项都超过90分
B 2项都超过90分
C 1项超过90分
D 平均分80以上
E 平均分75以上
F 不符合以上的都是F

分析

跟刚才的有些类似,但不同点是之前的条件是互斥的,但这里的条件并不是,满足高等级的很可能满足低等级,比如A等级的满足其他的所有条件,所有人也都可以划分为F等级。

所以这里处理是有顺序的,不满足高等级才会分到低等级。

实现

定义评级

public enum Rate {
    A, B, C, D, E, F
}

定义对选手的评价

@Data
public class Score {

    private Long uid;

    private int vocal;

    private int dance;

    private int rap;
}

定义获取评级的接口

public interface IRateService {

    Rate getRate(Score score);
    
}

按照刚才的思路,我们也可以定义每个级别handler

public interface IRateHandler {

    Rate getRate();

    /**
     * 条件是否满足
     *
     * @return
     */
    boolean isMatch(Score score);
}

实现就不都写了,要注意的是F等级应该是默认是true

@Component
public class RateAHandler implements IRateHandler {
    @Override
    public Rate getRate() {
        return Rate.A;
    }

    @Override
    public boolean isMatch(Score score) {
        return score.getVocal() > 90 && score.getDance() > 90 && score.getRap() > 90;
    }
}
@Component
public class RateFHandler implements IRateHandler {
    @Override
    public Rate getRate() {
        return Rate.F;
    }

    @Override
    public boolean isMatch(Score score) {
        return true;
    }
}

服务的实现与刚才类似,但是由于现在的条件不是那么单一的判断,无法使用map

@Service
public class RateServiceImpl implements IRateService {

    public List<IRateHandler> rateHandlers;

    public RateServiceImpl(List<IRateHandler> rateHandlers) {
        this.rateHandlers = rateHandlers;
    }

    @Override
    public Rate getRate(Score score) {
        for (IRateHandler rateHandler : rateHandlers) {
            if (rateHandler.isMatch(score)) {
                return rateHandler.getRate();
            }
        }
        throw new IllegalArgumentException("无法处理");
    }
}

这里还有个问题,就是handler应该是有顺序的,有两种实现思路

  • 在handler上加 @Order 注解,在注入的时候就已经排好序了
  • IRateHandler 上定义一个获取优先级的方法,然后在 RateServiceImpl 的构造函数里,根据优先级进行排序。

然后就搞定了。

后记

这种实现方式的使用场景还是很多的,应对更复杂的场景还可以提供一些公共的抽象类,减少一些重复的代码。

如果你有更好的实现方式也欢迎留言。

原文  https://segmentfault.com/a/1190000020454214
正文到此结束
Loading...