Избегайте запаха кода 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
было бы. Интересно для сложного наследия.