Java Factory Pattern With Generics
Solution 1
This is a VERY GOOD question.
You could cast brutely
return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);
The answer is theoretically flawed, since we force BASEBALL=Baseball
.
It works due to erasure. Actually it depends on erasure.
I hope there is a better answer that is reification safe.
Solution 2
This is a wrong design pattern. Rather than using one generic method and an if ladder, you should instead use overloading. Overloading eliminates the need for the if ladder and the compiler can make sure the correct method is invoked rather than having to wait till runtime.
eg.
public class BallUserInterfaceFactory {
public static BallUserInterface<Baseball> getUserInterface(
Baseball ball) {
return new BallUserInterface<Baseball>(ball);
}
public static BallUserInterface<Football> getUserInterface(
Football ball) {
return new BallUserInterface<Football>(ball);
}
}
This way you also get the added benefit of compile time errors if your code cannot create a BallUserInterface
for the appropriate ball.
To avoid the if ladder you can use a technique known as double dispatch. In essence, we use the fact that the instance knows what class it belongs to and calls the appropriate factory method for us. For this to work Ball
needs to have a method that returns the appropriate BallInterface
.
You can either make the method abstract or provide a default implementation that throws an exception or returns null. Ball and Baseball should now look something like:
public abstract class Ball<T extends Ball<T>> {
abstract BallUserInterface<T> getBallUserInterface();
}
.
public class Baseball extends Ball<Baseball> {
@Override
BallUserInterface<Baseball> getBallUserInterface() {
return BallUserInterfaceFactory.getUserInterface(this);
}
}
To make things a little neater, it's better to make getBallUserInterface
package private and provide a generic getter in BallUserInterfaceFactory
. The factory can then manage additional checks like for null and any thrown exceptions. eg.
public class BallUserInterfaceFactory {
public static BallUserInterface<Baseball> getUserInterface(
Baseball ball) {
return new BallUserInterface<Baseball>(ball);
}
public static <T extends Ball<T>> BallUserInterface<T> getUserInterface(
T ball) {
return ball.getBallUserInterface();
}
}
The Visitor Pattern
As pointed out in the comments, one problem of the above is it requires the Ball
classes to have knowledge of the UI, which is highly undesirable. You can, however, use the visitor pattern, which enables you to use double dispatch, but also decouples the various Ball
classes and the UI.
First, the necessary visitor classes, and factory functions:
public interface Visitor<T> {
public T visit(Baseball ball);
public T visit(Football ball);
}
public class BallUserInterfaceVisitor implements Visitor<BallUserInterface<? extends Ball>> {
@Override
public BallUserInterface<Baseball> visit(Baseball ball) {
// Since we now know the ball type, we can call the appropriate factory function
return BallUserInterfaceFactory.getUserInterface(ball);
}
@Override
public BallUserInterface<Football> visit(Football ball) {
return BallUserInterfaceFactory.getUserInterface(ball);
}
}
public class BallUserInterfaceFactory {
public static BallUserInterface<? extends Ball> getUserInterface(Ball ball) {
return ball.accept(new BallUserInterfaceVisitor());
}
// other factory functions for when concrete ball type is known
}
You'll note that the visitor and the factory function have to use wildcards. This is necessary for type safety. Since you don't know what type of ball has been passed, the method cannot be sure of what UI is being returned (other than it is a ball UI).
Secondly, you need to define an abstract accept
method on Ball
that accepts a Visitor
. Each concrete implementation of Ball
must also implement this method for the visitor pattern to work correctly. The implementation looks exactly the same, but the type system ensures dispatch of the appropriate methods.
public interface Ball {
public <T> T accept(Visitor<T> visitor);
}
public class Baseball implements Ball {
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
}
Finally, a bit of code that can put all this together:
Ball baseball = new Baseball();
Ball football = new Football();
List<BallUserInterface<? extends Ball>> uiList = new ArrayList<>();
uiList.add(BallUserInterfaceFactory.getUserInterface(baseball));
uiList.add(BallUserInterfaceFactory.getUserInterface(football));
for (BallUserInterface<? extends Ball> ui : uiList) {
System.out.println(ui);
}
// Outputs:
// ui.BaseballUserInterface@37e247e2
// ui.FootballUserInterface@1f2f0ce9
Related videos on Youtube
FuryComputers
Updated on June 04, 2020Comments
-
FuryComputers about 4 years
I would like my
BallUserInterfaceFactory
to return an instance of a user interface that has the proper generic type. I am stuck in the example below getting the error:Bound mismatch: The generic method getBaseballUserInterface(BASEBALL) of type BallUserInterfaceFactory is not applicable for the arguments (BALL). The inferred type BALL is not a valid substitute for the bounded parameter
public class BallUserInterfaceFactory { public static <BALL extends Ball> BallUserInterface<BALL> getUserInterface(BALL ball) { if(ball instanceof Baseball){ return getBaseballUserInterface(ball); } //Other ball types go here //Unable to create a UI for ball return null; } private static <BASEBALL extends Baseball> BaseballUserInterface<BASEBALL> getBaseballUserInterface(BASEBALL ball){ return new BaseballUserInterface<BASEBALL>(ball); } }
I understand that it cannot guarantee that BALL is a Baseball, and so there is a parameter type mismatch on the getBaseballUserInterface method call.
If I cast the ball parameter in the getBaseballUserInterface method call, then I get the error:
Type mismatch: cannot convert from
BaseballUserInterface<Baseball>
toBallUserInterface<BALL>
Because it can't guarantee that what I am returning is the same type of BALL.
My question is, what is the strategy for dealing with this situation?
(For completeness, here are the other classes required in the example)
public class Ball { } public class Baseball extends Ball { } public class BallUserInterface <BALL extends Ball> { private BALL ball; public BallUserInterface(BALL ball){ this.ball = ball; } } public class BaseballUserInterface<BASEBALL extends Baseball> extends BallUserInterface<BASEBALL>{ public BaseballUserInterface(BASEBALL ball) { super(ball); } }
-
ollins almost 12 yearsIs BaseballUserInterface extends BallUserInterface<BaseBall> not enough? Do you have Subclasses of BaseballUserInterface?
-
FuryComputers almost 12 years@ollins I was using that strategy originally, if you change the example to that, you will end up with another "Type mismatch" situation because there is no guarantee that the BALL type returned from the factory is the same as the BALL type in the BallUserInterface. (This time specified in the BaseballUserInterface class declaration).
-
-
artbristol almost 12 yearsIMHO that cast is safe, despite the warning. With reification, wouldn't the cast be checked at runtime and presumably always work?
-
FuryComputers almost 12 yearsI really like this answer, but here is the issue I have with it: If I have a Ball then I cant call getUserInterface without putting an if ladder somewhere else. For instance:
Ball ball = new Baseball(); BallUserInterface<?> ui = BallUserInterfaceFactory.getUserInterface(ball);
Does not work because there could be other types of Ball that are not overloaded in the factory method. -
artbristol almost 12 years@FuryComptuers But if you only have a
Ball
, you don't need the additional information a generic method will give you. You can just receiveBallUserInterface<?>
. -
Dunes almost 12 yearsI added a bit about using double dispatch to avoid if ladders.
-
HeavyE almost 12 yearsAn object should not have any information about its User Interface, so adding a UI return method to the object is not the correct solution.
-
HeavyE almost 12 yearsI should also add that it isn't even reasonable to assume that someone building a UI for a Ball object has the ability to change the Ball object. (though a wrapper could be created client-side)
-
FuryComputers over 11 yearsI have up voted this answer because think it may help others, but for me, it just moved the problem to other places in the code. I also agree with HeavyE that the object should not have any knowledge of it's UI.
-
FuryComputers over 11 yearsIt is unfortunate, but this seems to be the only solution that doesn't move the problem somewhere else in the code.