验证期间使用泛型

时间:2016-05-05 11:02:58

标签: java validation generics

我正在使用apache CXF。

以下API用于发布联系人。

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
ResponseResult create(@Context HttpHeaders httpHeaders, @Context Request request, @Context UriInfo uriInfo,
        UserContact contact) throws MDMException;

此处UserContact类包含有关用户的联系信息,该用户在正文中作为JSON传递。

我需要对此UserContact对象进行一些业务验证。但是我不喜欢在一个类中包含太多的验证代码。

我想做类似以下的事情。但我面临着泛型的问题。

interface Rule<S>
{
    void applyRule(S s)throws Exception;
}

interface Validatable
{
    void validate() throws Exception;
}

public class MyValidator
{
    private HashMap<? extends Rule ,?> map = new HashMap<>();

    public void validate() throws Exception
    {
        for(Rule rule : map.keySet())
        {
            rule.applyRule(map.get(rule));
        }
    }

    public <S> void addRule(Rule<S> rule, S data)
    {
        this.map.put(rule, data);
    }
}   


class EMailValidationRule implements Rule<String>
{
    private static final Pattern emailPattern = Pattern.compile("email-regex");
    public void applyRule(String s) throws Exception
    {
        if(!emailPattern.matcher(s).matches())
            throw new Exception("Not a valid EMail");
    }
}

因此UserContact必须执行以下操作以进行验证。这使代码保持紧凑(IMO)。

class UserContact implements Validatable
{

    // some 
    // code
    // related to User Contact


    public void validate() throws Exception
    {
        MyValidator validator = new MyValidator();
        validator.addRule(new EMailValidationRule(), "developer@stackoverflow.com");
        validator.addRule(new PhoneValidationRule(), "+1234567890");
        validator.validate();
    }
}

我一直收到如下错误:

HashMap类型中的put(捕获#5-of?extends Rule,capture#6-of?)不适用      对于参数(规则,S)

上述设计也适合做验证吗?

2 个答案:

答案 0 :(得分:2)

问题在于,尽管您的封装确保了它,但编译器无法确定检索到的Rule<...>是否具有与检索到的数据相同类型的类型参数。

还存在无法使用Rule<T>子类型的数据插入T的问题。如果您有Rule<S> rule, S data,那么类型必须完全匹配。虽然Rule<S>可以处理S的子类型,但很好。

虽然MyValidator是一个很酷的小班,但我真的没有意识到这一点。特别是因为每次拨打validate时都会创建一个新的。它也很难缓存,因为规则是静态的(对于每个类的实例都是相同的),数据来自单个实例(我假设)。

您也可以这样做:

class UserContact implements Validatable
{

    // some 
    // code
    // related to User Contact

    // 1 rule instance for the entire class, not a new one per call to 'validate'
    private static EMailValidationRule emailRule = new EmailValidationRule();
    private static PhoneValidationRule phoneRule = new PhoneValidationRule();

    public void validate() throws Exception
    {
        emailRule.applyRule("developer@stackoverflow.com");
        phoneRule.applyRule("+1234567890");
    }
}

从不如此,这是MyValidator的工作版本:

class MyValidator {
    private Map<Rule<?>, RuleNode<?>> map = new HashMap<>();

    public void validate() throws Exception {
        for(RuleNode<?> node : map.values())
            node.apply();
    }

    public <T, D extends T> void addRule(Rule<T> rule, D data) {
        @SuppressWarnings("unchecked") // unchecked, but safe due to encapsulation
        RuleNode<T> r = (RuleNode<T>) map.get(rule);
        if(r == null) {
            r = new RuleNode<T>(rule);
            map.put(rule, r);
        }

        r.add(data);
    }

    private static class RuleNode<T> { // Maintains that the rule and data are compatible
        private final Rule<T> rule;
        private final List<T> list = new ArrayList<>();

        public RuleNode(Rule<T> rule) {
            this.rule = rule;
        }

        public void add(T data) {
            list.add(data);
        }

        public void apply() throws Exception {
            for(T data : list)
                rule.applyRule(data);
        }
    }
}

答案 1 :(得分:2)

您只需要制作MyValidator

的通用版本

泛型类使用以下格式定义:

  

class name<T1, T2, ..., Tn> { /* ... */ }

使用泛型定义类,您将指定要在类中使用的类型,在您的情况下<R extends Rule<S> ,S>

class MyValidator<R extends Rule<S> ,S>{
    private HashMap<R ,S> map = new HashMap<>();

    public void validate() throws Exception{
        for(Rule<S> rule : map.keySet()){
            rule.applyRule(map.get(rule));
        }
    }
    public void addRule(R rule, S data){
        this.map.put(rule, data);
    }
}   

完成后,您只需构建所需类型的MyValidator

MyValidator<Rule<String>, String> validator = new MyValidator<>();

最后添加与验证器类型匹配的规则:

validator.addRule(new EMailValidationRule(), "developer@stackoverflow.com");

例如,添加一个您的UserContact看起来像的电话验证器:

class PhoneValidationRule implements Rule<String>{
    private static final Pattern phonePattern = Pattern.compile("phone-regex");
    public void applyRule(String s) throws Exception{
        if(!phonePattern.matcher(s).matches())
            throw new Exception("Not a valid phone");
    }
}

class UserContact implements Validatable{
    public void validate() throws Exception{
        MyValidator<Rule<String>, String> validator = new MyValidator<>();
        validator.addRule(new EMailValidationRule(), "developer@stackoverflow.com");
        validator.addRule(new PhoneValidationRule(), "developer@stackoverflow.com");
        validator.validate();
    }
}