Talk is cheap show me the code——优雅应对条件可配置的实体多字段校验

需求

管理后台创建多条招聘需求,分别配置招聘条件,对简历进行筛选

分析

一条招聘需求就是由各种招聘条件组成的,我们把每个招聘条件抽象成一个实体字段,再封装一个对应的校验逻
辑,应用于简历筛选。本需求同样适用于各种banner、广告等的选择性展示。

先在脑海了想象这个场景:你发布了一条招聘需求,然后你要对别人投的简历进行筛选。enough,Talk is cheap,show me the code。

1. 简化了的招聘条件实体类,省略getter/setter。


public class RecruitmentCondition {

    /**
     * 年龄要求,包括边界
     */
    @Validate(ResumeValidator.AGE)
    private Range<Integer> age;

    /**
     * 性别要求
     */
    @Validate(ResumeValidator.SEX)
    private Integer sex;
    /**
     * 技能要求
     */
    @Validate(ResumeValidator.SKILL)
    private Set<String> skills;
    /**
     * 婚姻要求
     */
    @Validate(ResumeValidator.MARITAL_STATUS)
    private Boolean maritalStatus;
}

2. 简化了的简历实体类,省略getter/setter

public class Resume {

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 性别
     */
    private Integer sex;
    /**
     * 技能
     */
    private String skill;
    /**
     * 婚姻
     */
    private Boolean maritalStatus;
}

3. 有了上面两个实体后,考虑校验逻辑。实现方式有很多,简单一点直接写个校验方法,把校验逻辑全部扔进去,但是这样不利于扩(zhuang)展(bi),我们把每个字段的校验逻辑独立开来,通过注解申明各个字段应该应用哪个封装好的校验逻辑。先声明一个校验器接口:

public interface Validator {
    boolean valid(RecruitmentCondition condition, Resume resume);
}

继承接口实现校验逻辑:

public enum ResumeValidator implements Validator {
    /**
     * 年龄校验逻辑
     */
    AGE {
        @Override
        public boolean valid(RecruitmentCondition condition, Resume resume) {
            Range<Integer> ageCondition = condition.getAge();
            if (ageCondition != null) {
                Integer userAge = resume.getAge();
                return ageCondition.contains(userAge);
            }
            return true;
        }
    },
    /**
     * 性别校验逻辑
     */
    SEX{
        @Override
        public boolean valid(RecruitmentCondition condition, Resume resume) {
            Integer sexCondition = condition.getSex();
            if (sexCondition != null) {
                return Objects.equals(sexCondition, resume.getSex());
            }
            return true;
        }
    },
    /**
     * 技能校验逻辑
     */
    SKILL {
        @Override
        public boolean valid(RecruitmentCondition condition, Resume resume) {
            Set<String> skillCondition = condition.getSkills();
            if (CollectionUtils.isNotEmpty(skillCondition)) {
                String userSkill = resume.getSkill();
                return userSkill != null && skillCondition.contains(userSkill);
            }
            return true;
        }
    },
    /**
     * 婚姻状态校验逻辑
     */
    MARITAL_STATUS {
        @Override
        public boolean valid(RecruitmentCondition condition, Resume resume) {
            Boolean marriageCondition = condition.getMaritalStatus();
            if (marriageCondition != null) {
                return Objects.equals(marriageCondition, resume.getMaritalStatus());
            }
            return true;
        }
    },
}

我们把每个字段的校验逻辑分开,以枚举的方式实现,如果需要增加新的条件,只要再加个枚举就是了,现有的代码无需变动,嗯,蛮灵活的。

现在考虑如何将条件字段与校验逻辑串联起来,没错,注解,相信你已经在RecruitmentCondition实体类中看到我标注的注解了,注解的value属性指定了该字段应用的校验逻辑。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {
    ResumeValidator value();
}

4. 将字段和校验逻辑关联起来后,我们只需要把对应的逻辑跑起来就行了,封装一个Predicate:

public class ResumeFilter implements Predicate<Resume> {

    private static final Set<Validator> validators = Arrays.stream(RecruitmentCondition.class.getDeclaredFields())
                                                           .filter(field -> field.isAnnotationPresent(Validate.class))
                                                           .map(field -> field.getAnnotation(Validate.class).value()).collect(Collectors.toSet());
    private RecruitmentCondition recruitmentCondition;

    public ResumeFilter(RecruitmentCondition recruitmentCondition) {
        this.recruitmentCondition = recruitmentCondition;
    }

    @Override
    public boolean test(Resume resume) {
        return validators.stream().allMatch(validator -> validator.valid(recruitmentCondition, resume));
    }
}

5. 最后,demo搞起来

public static void main(String[] args) {
        // 后台组招聘20-35岁的java或python工程师
        RecruitmentCondition backendCondition = new RecruitmentCondition();
        backendCondition.setAge(Range.between(20, 35));
        backendCondition.setSkills(Sets.newHashSet("java", "python"));
        // 测试组招聘20-28岁未婚的妹子测试工程师
        RecruitmentCondition testerCondition = new RecruitmentCondition();
        testerCondition.setAge(Range.between(20, 28));
        testerCondition.setSex(1);
        testerCondition.setSkills(Sets.newHashSet("测试"));
        testerCondition.setMaritalStatus(false);

        // 收集100份简历
        HashSet<Resume> resumes = Sets.newHashSetWithExpectedSize(100);
        Resume resume;
        Set<String> candidateSkills = Sets.newHashSet("java", "C", "C++", "python", "go", "产品经理", "测试", "UI");
        for (int i = 0; i < 100; i++) {
            resume = new Resume();
            resume.setAge(RandomUtils.nextInt(18, 50));
            resume.setSex(RandomUtils.nextInt(0, 2));
            resume.setSkill(RandomDataUtils.randomOne(candidateSkills).orElse("java"));
            resume.setMaritalStatus(RandomUtils.nextBoolean());
            resumes.add(resume);
        }
        // 筛选出符合要求的简历
        Set<Resume> backendResumes = resumes.stream().filter(new ResumeFilter(backendCondition)).collect(Collectors.toSet());
        Set<Resume> testerResumes = resumes.stream().filter(new ResumeFilter(testerCondition)).collect(Collectors.toSet());
    }

搞定,还有一些我们线上的各种banner啊,广告啊,如果需要对不同的用户做展示控制,都可以用这套,你学废了没有?!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值