2015-05-23 8 views
0

Я не могу найти оптимальный подход к решению следующей проблемы. Скажем, есть абстрактный базовый класс с несколькими конкретными подклассами:Избегайте литье типа во время обработки данных

public abstract class AbstractType { /* common properties */ } 

public class TypeA { /* properties of type A */ } 
public class TypeB { /* properties of type A */ }` 

Эти классы домена (JPA лица). Свойства типов (среди прочего) используются для проверки пользовательских данных. Я полагаю, что добавление логики к самой модели домена считается плохой практикой. Поэтому я хочу избежать добавления метода validate к конкретным подклассам. Как так:

UserInput userInput = ...; 
AbstractType data = ...; 
data.validate(userInput); 

Я не вижу вариант без необходимости отливать модель предметной области, , если я хочу, чтобы переместить логику к логическому слою. Имея ограниченные знания, я могу только придумать два похожих «решения», используя какой-то интерфейс обработчика.

  1. Держите некоторую прямую ссылку на обработчик в типе

    public interface TypeHandler { 
        public validate(AbstractType data, UserInput userInput); 
    } 
    
    /* TypeAHandler & TypeBHandler implementations */ 
    
    public enum Type { 
        TYPE_A(new TypeAHandler()), 
        TYPE_B(new TypeBHandler()); 
    
        private TypeHandler handler; 
    
        public Handler(TypeHandler handler){ 
         this.handler = handler; 
        } 
    
        public TypeHandler getHandler(){ return handler; } 
    } 
    
    public class TypeA { 
        private Type type = TYPE_A; 
        /* ... */ 
    } 
    

    Обработчик бы, чем можно назвать следующим образом:

    UserInput userInput = ...; 
    AbstractType data = ...; 
    data.getType.getHandler().validate(data, userInput); 
    

    Ссылка на обработчик также может быть добавлен сразу (без переименования между ними) как свойство класса AbstractType, но это будет означать, что есть ссылка на класс внутри логического уровня из модели домена (какой тип o f побеждает цель перемещения логики на логический уровень?)

    Проблема здесь также в том, что метод проверки внутри TypeXHandler должен передать аргумент data в свой подкласс прежде, чем он сможет начать проверку.

  2. Или я мог бы реализовать некоторый метод, который имеет большую структуру if-then, чтобы получить правильный подкласс, бросить его и вызвать соответствующий обработчик, который реализует интерфейс, подобный следующему.

    public interface TypeHandler<T extends AbstractType> { 
        public validate(T data, UserInput userInput); 
    } 
    

Таким образом, в обоих случаях есть литье. В первом случае нет огромной if-then структуры, но логика и домен не разделены. Во втором случае существует очень негибкая структура if-then.

В заключение, вот мой вопрос. Должен ли я действительно избегать реализации логики непосредственно внутри домена? Если да, есть ли способ избежать кастинга, структуры if-else и/или добавления дополнительных свойств в модель домена (например, перечисление в первом «решении»).

ответ

1

В конце дня вы разветвляетесь на основе подтипа (конкретные классы), поскольку логика для проверки ввода пользователя основана на тех конкретных деталях, которые содержатся в подклассах.

Дженерики действительно не поможет вам много здесь, так как дженерики в основном базируются на применении логики, которая однородна по различным типам, работающих на универсальной логики применяется к общего интерфейса, что все применяемые типы обмена. Здесь ваша логика и интерфейс различаются для каждого подтипа.

Таким образом, ваш основной выбор - это нерасторжимое решение, в котором вы изменяете центральный исходный код (например, большой пучок ifs/elses, карту и т. Д.) И вручную разветвляетесь на основе подтипа или используете абстракцию/динамический полиморфизм как расширяемое решение который не требует изменения какого-либо центрального исходного кода и автоматически разветвляется на основе подтипа.

Отражение также может быть возможным путем, если вы можете себе это позволить (это немного дорого стоит во время выполнения) и при условии, что он может поместиться, чтобы дать вам универсальную логику, которую вы можете реализовать централизованно.

Если вы не хотите, чтобы добавить этот validate метод AbstractType и всех его подтипов, то вы всегда можете добавить еще один уровень абстракции, на вершине которой действительно содержит validate метод, как ValidatorB, который реализует интерфейс IValidator и хранит объект TypeB в качестве члена и применяет логику, используемую для проверки ввода пользователя с использованием свойств TypeB's.

+0

Спасибо @Ий за ответ. Мне очень нравится параграф с объяснением дженериков! Помогает мне вставить дженерики в перспективу. Отражение действительно могло помочь в решении проблемы, но я думаю, что это слишком далеко, чтобы избежать логики в сущности. – badoit

+0

Вы считаете, что композиция здесь будет хорошей подгонке?Имея класс 'Data' со свойствами' TypeA typeAcontent' и 'TypeB typeBcontent' и передавая экземпляр' Data' методу 'оценки' правой реализации IValidator'? Может ли это стать беспорядочным, если число конкретных классов увеличивается? (мне кажется, что это будет самый чистый вариант) – badoit

+0

Я думаю, что это зависит от ваших потребностей в расширении - сколько из этих реализаций AbstractType вы будете иметь, изменится ли это в будущем и т. д. Мне конечная разница заключается в том, будете ли вы проверять типы в каком-то центральном месте (менее расширяемы, но могут быть не такими плохими для небольшого числа типов) или с использованием полиморфизма. –

0

Я изучил образцы дизайна на прошлой неделе, и я хотел бы предложить свое решение (оно работает, но я не уверен, что это самый умный способ решить вашу проблему).

Идея моего решения заключается в использовании фабрики: вы даете модель (в вашем случае сущность JPA) на завод и дает вам правильный валидатор для этой модели.

В начале программы вы должны указать фабрике, которая является классом валидатора для каждого класса модели вашей программы с помощью метода регистрации.

Начнем с реализации ...

AbstractModel.java

public abstract class AbstractModel 
{ 
    private final int commonProperty; 

    protected AbstractModel(int commonProperty) 
    { 
     this.commonProperty = commonProperty; 
    } 

    public int getCommonProperty() { return commonProperty; }; 
} 

В AbstractModel мы помещаем все общие свойства моделей.

ModelA.java

public class ModelA extends AbstractModel 
{ 
    private final int specificProperty1; 
    private final int specificProperty2; 

    public ModelA(int commonProperty, int specificProperty1, int specificProperty2) 
    { 
     super(commonProperty); 

     this.specificProperty1 = specificProperty1; 
     this.specificProperty2 = specificProperty2; 
    } 

    public int getSpecificProperty1() { return specificProperty1; } 

    public int getSpecificProperty2() { return specificProperty2; } 
} 

ModelA имеет две специфические свойства.

ModelB.java

public class ModelB extends AbstractModel 
{ 
    private final int specificProperty1; 
    private final int specificProperty2; 

    public ModelB(int commonProperty, int specificProperty1, int specificProperty2) 
    { 
     super(commonProperty); 

     this.specificProperty1 = specificProperty1; 
     this.specificProperty2 = specificProperty2; 
    } 

    public int getSpecificProperty1() { return specificProperty1; } 

    public int getSpecificProperty2() { return specificProperty2; } 
} 

ModelB имеет две специфические свойства тоже.


Давайте предположим, что экземпляр a из ModelA справедливо тогда и только тогда

a.commonProperties == a.specificProperty1 + a.specificProperty2 

и экземпляр b из ModelB справедливо тогда и только тогда

b.commonProperties == b.specificProperty1 * b.specificProperty2 

Validator.java

public interface Validator 
{ 
    public boolean validate(); 
} 

Действительно простой интерфейс для валидаторов.

AbstractValidator.java

public abstract class AbstractValidator implements Validator 
{ 
    private final AbstractModel toBeValidated; 

    protected AbstractValidator(AbstractModel toBeValidated) 
    { 
     this.toBeValidated = toBeValidated; 
    } 

    protected AbstractModel getModel() 
    { 
     return toBeValidated; 
    } 
} 

Это суперкласс конкретных валидаторов, которые оборачивают модель для проверки.

ValidatorA.java

public class ValidatorA extends AbstractValidator 
{ 
    protected ValidatorA(AbstractModel toBeValidated) 
    { 
     super(toBeValidated); 
    } 

    public boolean validate() 
    { 
     ModelA modelA = (ModelA) getModel(); 

     return modelA.getCommonProperty() == modelA.getSpecificProperty1() + modelA.getSpecificProperty2(); 
    } 
} 

Валидатор для экземпляров ModelA.

ValidatorB

public class ValidatorB extends AbstractValidator 
{ 
    protected ValidatorB(AbstractModel toBeValidated) 
    { 
     super(toBeValidated); 
    } 

    public boolean validate() 
    { 
     ModelB modelB = (ModelB) getModel(); 

     return modelB.getCommonProperty() == modelB.getSpecificProperty1() * modelB.getSpecificProperty2(); 
    } 
} 

И это валидатор для экземпляров ModelB.


И, наконец, фабрика!


ValidatorFactory.Java

public class ValidatorsFactory 
{ 
    private static ValidatorsFactory instance; 
    private final HashMap<Class<? extends AbstractModel>, Class<? extends Validator>> registeredValidators; 

    private ValidatorsFactory() 
    { 
     registeredValidators = 
       new HashMap<Class<? extends AbstractModel>, Class<? extends Validator>>(); 
    } 

    public static ValidatorsFactory getInstance() 
    { 
     if (instance == null) 
      instance = new ValidatorsFactory(); 

     return instance; 
    } 

    public void registerValidator(
      Class<? extends AbstractModel> model, 
      Class<? extends Validator> modelValidator) 
    { 
     registeredValidators.put(model, modelValidator); 
    } 

    public Validator createValidator(AbstractModel model) 
    { 
     Class<? extends Validator> validatorClass = registeredValidators.get(model.getClass()); 
     Constructor<? extends Validator> validatorConstructor = null; 
     Validator validator = null; 

     try 
     { 
      validatorConstructor = validatorClass.getDeclaredConstructor(new Class<?>[] { AbstractModel.class }); 
      validator = (Validator) validatorConstructor.newInstance(new Object[] { model }); 
     } 
     catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) 
     { 
      System.err.println(e.getMessage()); 
      // handle exception 
     } 

     return validator; 
    } 
} 

Завод одноэлементно с двумя знаменательными методом:

  1. registerValidator добавить новую пару (modelClass, validatorClass) в HashMap.
  2. createValidator, чтобы получить правильный валидатор для указанной модели.

Это, как использовать этот шаблон:

public class Main 
{ 
    public static void main(String args[]) 
    { 
     ValidatorsFactory factory = ValidatorsFactory.getInstance(); 

     factory.registerValidator(ModelA.class, ValidatorA.class); 
     factory.registerValidator(ModelB.class, ValidatorB.class); 

     ModelA modelA = new ModelA(10, 4, 6); 
     if (factory.createValidator(modelA).validate()) 
      System.out.println("modelA is valid"); 
     else 
      System.out.println("modelA is not valid"); 

     ModelB modelB = new ModelB(10, 8, 2); 
     if (factory.createValidator(modelB).validate()) 
      System.out.println("modelB is valid"); 
     else 
      System.out.println("modelB is not valid"); 
    } 
} 

выход:

modelA is valid [because 10 = 4 + 6] 
modelB is not valid [because 10 != 8 * 2] 

Обратите внимание, что модель полностью separeted от контроллера, и он использует только один бросок из AbstractModel к конкретной модели.

Надеюсь, это поможет!

+0

Спасибо @BlackBrain за быстрый и продуманный ответ. «Карта» от модели до валидатора внутри «ValidatorsFactory» действительно решает большую if-then структуру второго «решения», о котором я упоминал. Тем не менее, нам по-прежнему приходится использовать 'AbstractModel' для своего конкретного подкласса внутри реализаций Validator. Мне интересно, можем ли мы решить это с помощью дженериков. – badoit

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