Избегайте запаха кода If-else при создании объектов, которые зависят от конкретных условий

Есть ли лучший способ справиться с экземпляром объекта (Product) который зависит от другого типа объекта (Condition), чем использование if-else в паре с instanceof как показывает следующий код?

import java.util.ArrayList;
import java.util.List;

abstract class AbstractProduct {
    private AbstractCondition condition;
    public AbstractProduct(AbstractCondition condition) {
        this.condition = condition;
    }
    public abstract void doSomething();
}

class ProductA extends AbstractProduct {
    AbstractCondition condition;
    public ProductA(AbstractCondition condition) {
        super(condition);
    }

    @Override
    public void doSomething() {
        System.out.println("I'm Product A");
    }
}

class ProductB extends AbstractProduct {    
    public ProductB(AbstractCondition condition) {
        super(condition);
    }   

    @Override
    public void doSomething() {
        System.out.println("I'm Product B");
    }
}

class AbstractCondition { }

class ConditionA extends AbstractCondition { }

class ConditionB extends AbstractCondition { }

public class Try {
    public static void main(String[] args) {
        List<AbstractCondition> conditions = new ArrayList<AbstractCondition>();
        List<AbstractProduct> products = new ArrayList<AbstractProduct>();

        conditions.add(new ConditionA());               
        conditions.add(new ConditionB());               
        conditions.add(new ConditionB());               
        conditions.add(new ConditionA());

        for (AbstractCondition c : conditions) {
            tryDoSomething(c);
        }
    }

    public static void tryDoSomething(AbstractCondition condition) {
        AbstractProduct product = null;
        if (condition instanceof ConditionA) {
            product = new ProductA(condition);
        } else if (condition instanceof ConditionB) {
            product = new ProductB(condition);
        }
        product.doSomething();
    }
}

Разница с кодом выше моего реального кода заключается в следующем: я не имею прямого контроля над AbstractCondition и его подтипы (как они есть в библиотеке), но создание конкретного подтипа AbstractProduct зависит от конкретного состояния.

Моя цель: стараться избегать if-else Код запаха в tryDoSomething(),

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

Другими словами, я хотел бы решить проблему только с помощью хороших принципов ООП (например, использование полиморфизма) и использовать некоторые шаблоны проектирования (которые, по-видимому, я не знаю в данном конкретном случае).

3 ответа

Поскольку вы не можете редактировать исходные объекты, вам необходимо создать статическую карту из типа условия в тип продукта:

private static HashMap< Class<? extends AbstractCondition>, 
                        Class<? extends AbstractProduct>
                      > conditionToProduct;`

Заполните его в статической инициализации парами Условие, Продукт:

static { 
  conditionToProduct.put(ConditionA.class, ProductA.class); 
  ... 
} 

и во время выполнения просто запросить карту:

Class<? extends AbstractProduct> productClass = conditionToProduct.get(condition.getClass());
productClass.newInstance();

AbstractCondition Нужно знать либо тип, либо как создать продукт.

Поэтому добавьте одну из следующих функций в AbstractCondition

Class<? extends AbstractProduct> getProductClass()

или же

AbstractProduct createProduct()

Вы должны создать класс Factory, чтобы помочь вам в этом.

interface IFactoryProduct{
    AbstractProduct getProduct(AbstractCondition condition)  throws Exception;
}

Это будет ваш интерфейс, просто нужно реализовать его следующим образом.

class FactoryProduct implements IFactoryProduct{

    public AbstractProduct getProduct(AbstractCondition condition) throws Exception{
        return (AbstractProduct)getClass().getMethod("getProduct", condition.getClass()).invoke(this, condition);
    }

    public ProductA getProduct(ConditionA condition){
        return new ProductA();
    }

    public ProductB getProduct(ConditionB condition){
        return new ProductB();
    }

}

Использование рефлексии для перенаправления с правильным методом сделает свое дело. это можно модернизировать для подклассов, если хотите.

РЕДАКТИРОВАТЬ:

Пример:

    List<AbstractCondition> list = new ArrayList<AbstractCondition>();
    list.add(new ConditionA());
    list.add(new ConditionB());

    for(AbstractCondition c : list){
        try {
            System.out.println(f.getProduct(c));
        } catch (Exception ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

labo.ProductA@c17164

labo.ProductB@1fb8ee3

Более сложная версия рефлексии, позволяющая получить подкласс:

public AbstractProduct getProduct(AbstractCondition condition) throws Exception{
    Method m = getMethodFor(condition.getClass());
    if(m == null )
        throw new Exception("No method for this condition " + condition.getClass().getSimpleName());
    else
        return (AbstractProduct) m.invoke(this, condition);
}

private Method getMethodFor(Class<? extends AbstractCondition> clazz ) throws Exception{
    try {
        return getClass().getMethod("getProduct", clazz);
    } catch (NoSuchMethodException ex) {
        if(clazz.getSuperclass() != AbstractCondition.class){
            return getMethodFor((Class<? extends AbstractCondition>)clazz.getSuperclass());
        }
        return null;
    }
}

Это позволяет мне отправить ConditionC extending ConditionB чтобы построить тот же продукт имеет ConditionB было бы. Интересно для сложного наследия.

Другие вопросы по тегам