2016-05-05 3 views
2

Я использую 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. Но мне не нравится иметь слишком много кода проверки, упакованного в один класс.

Я хотел бы сделать что-то вроде следующего. Но я сталкиваюсь с проблемой с Generics.

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(), "[email protected]"); 
     validator.addRule(new PhoneValidationRule(), "+1234567890"); 
     validator.validate(); 
    } 
} 

Я получаю ошибки как:

метод положить (? Захвата # 5-оф распространяется правило, захват # 6-в) в типа HashMap не применяется для аргументов (Правило , S)

Также представленная конструкция подходит для проведения валидации?

ответ

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("[email protected]"); 
     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); 
     } 
    } 
} 
+0

'вы перезаписать данные, которые были уже there.' только если правила равны, это все-таки «HashMap». Может быть полезно отделить правила проверки для одного и того же типа данных. Таким образом, одно правило может проверять шаблон электронной почты, а второй может гарантировать, что он не является дубликатом. С вашим кодом будет доступен только один. Это не обязательно то, что было запрошено. – Obicere

+0

@Obicere '' 'Может быть полезно отделить правила проверки для одного и того же типа данных''', это все еще возможно. У вас есть класс '' 'EMailvalidationRule реализует правило ' '', а другой класс '' 'AnotherValidationRule реализует правило ' ''. Они - разные ключи, поэтому у вас все еще есть и то, и другое. –

+0

Ваша первая точка поднимает интересный момент, вы не можете иметь 2 экземпляра правил того же типа, которые выполняют разные вещи (например, из-за разных полей экземпляра). Я взял из кода OP, что намерение состояло в том, чтобы иметь 1 экземпляр правила для каждого типа правила, но это также является слабым местом. См. Обновленный ответ –

2

Вам просто нужно сделать родовой вариант MyValidator Класс

Общий класс определяется с помощью формата thss:

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(), "[email protected]"); 

Так, например addind телефонный валидатор ваш 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(), "[email protected]"); 
     validator.addRule(new PhoneValidationRule(), "[email protected]"); 
     validator.validate(); 
    } 
} 
+0

Очень простое правило: Карта .put (...) не может быть вызвана, когда-либо. "?" это означает «какой-то класс, отличный от любого другого класса, который у вас может быть». Поэтому никакая ценность никогда не бывает достаточной для второго аргумента put(). Это отличается от get(), который просто обрабатывает «?» как Объект с целью определения типа, который он должен вернуть. – Arkadiy

Смежные вопросы