0

I sometimes run into this scenario and not sure if I'm using a bad approach or I just don't know how to solve it.

Let's say I have two classes and two beans like this:

public class BeanOne {
    public void methodBeanOne() {
        //...
    }
}

public class BeanTwo {
    public void methodBeanTwo() {
        //...
    }
}

public class ClassOne {
    private BeanOne bean;

    public ClassOne(BeanOne bean) {
        this.bean = bean;
    }

    public void methodclassOne() {
        bean.methodBeanOne();
    }
}

public class ClassTwo {
    private BeanTwo bean;

    public ClassTwo(BeanTwo bean) {
        this.bean = bean;
    }

    public void methodClassTwo() {
        bean.methodBeanTwo();
    }
}

I want to make a generic abstract class , so I can extract some logic from ClassOne and ClassTwo to it, and an abstract bean with common methods as well:

public abstract class AbstractBean {
    public void commonMethod() {
        //...
    }
}

public class BeanOne extends AbstractBean {
    public void methodBeanOne() {
        //...
    }
}

public class BeanTwo extends AbstractBean {
    public void methodBeanTwo() {
        //...
    }
}

public abstract class AbstractClass<T extends AbstractBean> {
    protected T bean;

    public AbstractClass(T bean) {
        this.bean = bean;
    }

    public void commonClassMethod(){
        bean.commonMethod();
    }
}

public class ClassOne extends AbstractClass<BeanOne> {

    public ClassOne(BeanOne bean) {
        super(bean);
    }

    public void methodclassOne() {
        bean.methodBeanOne();
    }
}

public class ClassTwo extends AbstractClass<BeanTwo> {

    public ClassTwo(BeanTwo bean) {
        super(bean);
    }

    public void methodClassTwo() {
        bean.methodBeanTwo();
    }
}

So far, so good.

The next step would be to create a factory to get one implementation based on an enum for instance, and here is where I start getting errors:

public class ClassFactory {

    public enum MyEnum {
        ONE, TWO;
    }

    private ClassFactory() {
    }

    public static AbstractClass newInstance(MyEnum value, AbstractBean bean) {
        switch(value){
        case ONE:
            return new ClassOne(bean);
        case TWO:
            return new ClassTwo(bean);
        default:
            throw new IllegalArgumentException();
        }
    }
}

This gives the following compilation errors:

The constructor ClassOne(AbstractBean) is undefined
The constructor ClassTwo(AbstractBean) is undefined

I've also tried:

public class ClassFactory {

    public enum MyEnum {
        ONE, TWO;
    }

    private ClassFactory() {
    }

    public static <T extends AbstractBean> AbstractClass<T> newInstance(MyEnum value, T bean) {
        switch(value){
        case ONE:
            return new ClassOne(bean);
        case TWO:
            return new ClassTwo(bean);
        default:
            throw new IllegalArgumentException();
        }
    }
}

But then I get:

Type mismatch: cannot convert from ClassOne to AbstractClass<T>
Type mismatch: cannot convert from ClassTwo to AbstractClass<T>

And I'm pretty much stuck there. I think I understand this error, but then, is it possible to create such a factory class trying to avoid castings?

I've also checked this post, but can't fully understand how it'd help me.

EDIT: Visitor Pattern

Ok, so I've tried the Visitor Pattern described in the previos post:

public interface Visitor<T> {
    T visit(BeanOne bean);

    T visit(BeanTwo bean);
}

public abstract class AbstractBean {
    public void commonMethod() {
        // ...
    }

    public abstract <T> T accept(Visitor<T> visitor);
}

public class BeanOne extends AbstractBean {
    public void methodBeanOne() {
        // ...
    }

    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }
}

public class BeanTwo extends AbstractBean {
    public void methodBeanTwo() {
        // ...
    }

    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }
}

public class ClassFactory {
    private ClassFactory() {
    }

    public static AbstractClass<? extends AbstractBean> newInstance(AbstractBean bean) {
        return bean.accept(new AbstractClassVisitor());
    }
}

public class AbstractClassVisitor implements Visitor<AbstractClass<? extends AbstractBean>> {

    @Override
    public AbstractClass<? extends AbstractBean> visit(BeanOne bean) {
        return ClassFactory.newInstance(bean);
    }

    @Override
    public AbstractClass<? extends AbstractBean> visit(BeanTwo bean) {
        return ClassFactory.newInstance(bean);
    }
}

But using it like this:

AbstractBean bean = new BeanOne();
AbstractClass<? extends AbstractBean> clazz = ClassFactory.newInstance(bean);
clazz.commonClassMethod();

I'm getting the following exception:

Exception in thread "main" java.lang.StackOverflowError
    at test.AbstractClassVisitor.<init>(AbstractClassVisitor.java:3)
    at test.ClassFactory.newInstance(ClassFactory.java:9)
    at test.AbstractClassVisitor.visit(AbstractClassVisitor.java:7)
    at test.AbstractClassVisitor.visit(AbstractClassVisitor.java:1)
    at test.BeanOne.accept(BeanOne.java:10)
    at test.ClassFactory.newInstance(ClassFactory.java:9)
    at test.AbstractClassVisitor.visit(AbstractClassVisitor.java:7)
    at test.AbstractClassVisitor.visit(AbstractClassVisitor.java:1)
    at test.BeanOne.accept(BeanOne.java:10)
    at test.ClassFactory.newInstance(ClassFactory.java:9)
    at test.AbstractClassVisitor.visit(AbstractClassVisitor.java:7)
    at test.AbstractClassVisitor.visit(AbstractClassVisitor.java:1)
    at test.BeanOne.accept(BeanOne.java:10)
    ...

I can see why this is happening, am I missing something?

Community
  • 1
  • 1
carcaret
  • 3,238
  • 2
  • 19
  • 37

2 Answers2

1

From the question you supplied yourself, see this answer: https://stackoverflow.com/a/12630501/144302

The same principle applies to your problem: use either a factory with overloaded methods

public class ClassFactory {

  private ClassFactory() {
  }

  public static AbstractClass<BeanOne> newInstance(BeanOne bean) {
    return new ClassOne(bean);
  }

  public static AbstractClass<BeanTwo> newInstance(BeanTwo bean) {
    return new ClassTwo(bean);
  }
}

or, as pointed out in the answer, apply a principle like double dispatch where you add a method AbstractClass<T> newInstance() to AbstractBean and implement it appropriately in each specialization. E.g.

class BeanOne { /* ... */ 
   public AbstractBean<BeanOne> newInstance() {
      return ClassFactory.newInstance(this);
   }
}

You can finally add the following method to ClassFactory

public static <T> AbstractClass<T> newInstance(AbstractBean<T> bean) {
   return bean.newInstance();
}

For specific advantages and disadvantages, I would encourage you to read the whole answer in the other question (it's very good and I don't want to copy other's work blindly).

Community
  • 1
  • 1
Stephan
  • 7,360
  • 37
  • 46
  • I've edited my answer adding the Visitor Pattern approach described on the other thread, but now I'm getting a `StackOverflowError`. Am I missing something? – carcaret Oct 02 '15 at 11:34
  • What does `AbstractClassVisitor` do? – Stephan Oct 02 '15 at 12:00
  • Sorry, forgot to include that class. – carcaret Oct 02 '15 at 12:10
  • In the abstract visitor class you have to return the concrete instances of `ClassOne` and `ClassTwo` instead of referring back to the factory method or you'll add specific methods to the factory class receiving `BeanOne` and `BeanTwo` as parameter. – Stephan Oct 02 '15 at 12:35
0

I think it is because of the constructor argument public ClassOne(BeanOne bean). You tried to supplies AbstractBean which is parent of BeanOne as the constructor argument. Java only allows object widening not narrowing. Try to type cast the parent object into specific child as follows:

public static AbstractClass newInstance(MyEnum value, AbstractBean bean) {
    switch(value):
    case ONE:
        return new ClassOne((BeanOne)bean);
    case TWO:
        return new ClassTwo((BeanTwo)bean);
    default:
        throw new IllegalArgumentException();
}

But make sure when you invoke the newInstance method, provides the correct object instance. Otherwise ClassCastException might occur.

Edit:

public static AbstractClass newInstance(MyEnum value) {
    switch(value):
    case ONE:
        return new ClassOne(new BeanOne());
    case TWO:
        return new ClassTwo(new BeanTwo());
    default:
        throw new IllegalArgumentException();
}
additionster
  • 628
  • 4
  • 14
  • Yeah, that's why I wanted to know if there's a way to accomplish this without castings. – carcaret Oct 02 '15 at 09:01
  • Then change the newInstance method as above – additionster Oct 02 '15 at 09:05
  • But this way you're just removing the `bean` instance I'm passing to the factory and instantiating a new one. I could perhaps use a copy constructor like `new BeanOne(bean)`. What still bugs me is the warning `AbstractClass is a raw type. References to generic type AbstractClass should be parameterized`, which can be solved by adding this `AbstractClass>`. Still, is this the correct approach or just some hacks to get this working even though it should be done in another way? – carcaret Oct 02 '15 at 09:19