5

Is there a better way to deal with an instanciation of an object (Product) which depends upon another object type (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 above of my real code is: I have NO direct control over AbstractCondition and its subtypes (as they are in a library), but the creation of a concrete subtype of AbstractProduct depends on the concrete condition.

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

I would also like to avoid reflection because it feels like cheating and I do think it's not an elegant, clean and readable solution.

In other words, I would like to tackle the problem just with good OOP principles (e.g. exploiting polymorphism) and pheraps some design patterns (which apparently I don't know in this specific case).

6
  • you can pass class name as string and use switch statement to avoid if else Commented Oct 27, 2016 at 9:46
  • 1
    Add to AbstractProduct a method buildProduct, implements in each class like you want. Commented Oct 27, 2016 at 9:50
  • switch is not going to work, because basically it's just the same as if-else (with a minor tweak). I would like to exploit the OOP, I prefer not using branching. Commented Oct 27, 2016 at 9:50
  • @AxelH how? Honestly, I don't think it will work. Commented Oct 27, 2016 at 10:02
  • 2
    I would also think about if it is really necessary to mirror each Condition with a Product. Of course it depends on the rest of your code, but duplicating class hirarchies is not so nice in itself. Maybe you can implement it in a diferent way, not creating one Product per Condition but one general Product (probably with some strategies for specific behaviour)!? Commented Oct 27, 2016 at 10:15

3 Answers 3

3

Since you can't edit the original objects, you need to create a static map from condition type to product type:

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

Fill it in static initialization with the pairs of Condition,Product:

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

and in runtime just query the map:

Class<? extends AbstractProduct> productClass = conditionToProduct.get(condition.getClass());
productClass.newInstance();
Sign up to request clarification or add additional context in comments.

2 Comments

But I'm not amending a previous approach, I'm offering a totally different solution
Because the first one doesn't occurs to answer the question, so edit it instead.
2

AbstractCondition needs to know either the type or how to construct a product.

So add one of the following functions to AbstractCondition

Class<? extends AbstractProduct> getProductClass()

or

AbstractProduct createProduct()

2 Comments

I prefer the abstract AbstractProduct createProduct() in the AbstractCondition. Cleaner :)
I can't modify AbstractCondition and its subtypes, they are in a library.
0

You should create a Factory class to help you with that then.

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

This will be your interface, 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 the reflexion to redirect with the correct method will do the trick. this is upgradable for subclassed if you want.

EDIT:

Some 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 complexe reflexion version allowing a subclass to be received :

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 ConditionC extending ConditionB to build the same product has ConditionB would. Interesting for complexe heritage.

7 Comments

It won't work. it will always get into getProduct(AbstractCondition condition) never reaching the other methods in override. That's because getProduct method cares about the static type not the dynamic one. And the static type in my method tryDoSomething() is AbstractCondition.
@1d0m3n30 Yes, you are right, I figure that out myself ... I am working on this ^^
@1d0m3n30, I knew this was possible, just using some (unsafe) reflection ;)
Factory pattern is absolutely right idea for this problem. The implementation in the answer is problematic but Factory that accept Condition argument and returns appropriate subtype of Product is the way to go
@MichaelGantman, this is not the cleanest solution, but the factory pattern is not design to work without some condition. get("this") or get("that") will use condition to redirect to the correct method. But I might miss something here ...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.