Avoid If-else code smells by creating objects that depend on specific conditions

Is there a better way to handle instanciation of an object ( Product ), which depends on a different type of object ( Condition ), than using if-else paired with instanceof , as the following code shows?

 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(); } } 

the difference with the code is above my real code: I have NO direct control over AbstractCondition and its subtypes (as they are in the library), but the creation of a specific AbstractProduct subtype depends on the specific condition.

My goal: try to avoid the smell of if-else code in tryDoSomething() .

I would also like to avoid reflection, because it seems like a hoax, and I think this is not an elegant, clean and clear solution.

In other words, I would like to solve the problem only with good OOP principles (for example, using polymorphism) and pheraps some design patterns (which, apparently, I do not know in this particular case).

+5
source share
3 answers

Since you cannot edit the source objects, you need to create a static map from the condition type to the product type:

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

Fill it in static initialization with Condition, Product pairs:

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

and at runtime just request a map:

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

AbstractCondition must know the type or method of creating the product.

So, add one of the following functions to AbstractCondition

Class<? extends AbstractProduct> getProductClass()

or

AbstractProduct createProduct()

+2
source

You should create a Factory class to help you with this.

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

This will be your interface, you just need to implement it like this.

 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(); } 

}

Using reflection to redirect with the correct method will do the trick. it's possible for a subclass if you want.

EDIT:

Example:

  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

A more complex version of reflection, allowing you to get a subclass:

 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; } } 

This allows me to send a ConditionC extending ConditionB to build the same product that ConditionB has. Interesting for a complex heritage.

0
source

Source: https://habr.com/ru/post/1258882/


All Articles