tags:

views:

82

answers:

3

I am trying to do something I would not normally do, it is a bit odd, but I'd like to make it work. Essentially I have a factory that has to create objects by calling the constructor with different types of data (A and B take different types in the code below). I seem to have gotten my self stuck going down the generics route (I do need the code to be as compile time typesafe as possible). I am not opposed to writing the code differently (I'd like to keep the idea of the factory if possible, and I do not want to have to add in casts - so the "data" parameter cannot be an "Object").

Any thoughts on how to fix the code with generics or an alternative way of doing it that meets my requirements?

(Technically this is homework, but I am the instructor trying out something new... so it isn't really homework :-)

public class Main2
{
    public static void main(String[] args) 
    {
        X<?> x;

        x = XFactory.makeX(0, "Hello");
        x.foo();

        x = XFactory.makeX(1, Integer.valueOf(42));
        x.foo();
    }

}

class XFactory
{
    public static <T> X<T> makeX(final int i,
                                 final T   data)
    {
        final X<T> x;

        if(i == 0)
        {
            // compiler error: cannot find symbol constructor A(T)
            x = new A(data);
        }
        else
        {
            // compiler error: cannot find symbol constructor B(T)
            x = new B(data);
        }

        return (x);
    }
}

interface X<T>
{
    void foo();
}

class A
    implements X<String>
{
    A(final String s)
    {
    }

    public void foo()
    {
        System.out.println("A.foo");
    }
}

class B
    implements X<Integer>
{
    B(final Integer i)
    {
    }

    public void foo()
    {
        System.out.println("B.foo");
    }
}
+1  A: 

I don't think what you're trying to do is possible without casting.

With casting, you have two options

if(i == 0)
        {
            x = new A((Integer)data);
        }
        else
        {
            x = new B((String)data);
     }
}

or

class A
    implements X<String>
{
    A(final Object s)
    {
    }
}

...

class B
    implements X<Integer>
{
    B(final Object i)
    {
    }
}
Milan Ramaiya
Casting (the first way) won't work - just moved the compiler error to the x = part. The second way is just something I will not do (it is essentially what I am trying to get rid of).
TofuBeer
Also casting (the first way) won't work because you can pass an Integer instead of a String and it'll compile but fail at runtime... I think I need to re-think the whole thing.
TofuBeer
+1  A: 

Probably the closest thing you could get whilst retaining static type safety and having lazy construction is:

public static void main(String[] args) {
    X<?> x;

    x = aFactory("Hello").makeX();
    x.foo();

    x = bFactory(42).makeX();
    x.foo();
}

private static XFactory aFactory(final String value) {
    return new XFactory() { public X<?> makeX() {
        return new A(value);
    }};
}

public static XFactory bFactory(final Integer value) {
    return new XFactory() { public X<?> makeX() {
        return new B(value);
    }};
}

interface XFactory() {
     X<?> makeX();
}

So we create an instance of an abstract factory that creates the appropriate instance with the appropriate argument. As a factory, the product is only constructed on demand.

Clearly something had to give. What would you expect XFactory.makeX(1, "Hello") to do?

Tom Hawtin - tackline
Still not going to work for what I want unfortunately - having to call 2 methods won't work as it defeats putting the code behind a factory method to begin with - but I'll play with it a bit and see. Essentially what I am after is a method that can create different things with a different parameter to the constructor without casting... so what I would like the "XFactory.makeX(1, "Hello") to do is fail at compile time... I just couldn't think of a way to do it. I did come up with a few ways but they were pretty ugly. I think I am just trying to push generics too hard.
TofuBeer
+1  A: 

I don't see a way to make it work. I don't really think it should work either. When calling your makeX() function the calling code needs to know what integer parameter corresponds to what type of data to pass in. IOW, your abstraction is very leaky in the first place, and what you're really implementing is a rudimentary form of polymorphism, which you might as well use method overloading for, i.e.:

X makeX(String data) {
  return new A(data);
}

X makeX(Integer data) {
  return new B(data);
}

Of course it's a toy problem and all that. One way to make it work would be to make the client aware of implementation classes and add a Class<T> argument that you instantiate through reflection. But I suppose that would be kind of defeating the purpose.

wds
I isn't actually a toy, but yeah it isn't super critical. I am leaning towards this way of doing it or changing the argument type to just be a String instead. In reality the string comes from argv and represents either a file name or a JDBC connection string.
TofuBeer
Okay, I was just going with "How would I implement this" and that's basically what came out.
wds