The problem is in some parts of your code you use generics, and in other parts you do not. You can go the unsafe way and have one factory method that returns different types of weigher from one method. However, you'll lose the type safety you get from making your weighers generic. eg.
Plant banana = ...;
Plant spinach = ...;
Weigher<Plant> bananaWeigher = WeigherFactory.getInstance(banana);
bananaWeigher.weigh(spinach); // Compiles, but will produce a runtime error!
The other way is to make WeigherFactory have different methods for each concrete Plant
class and to make the code that uses the WeigherFactory
generic as well. eg
public class WeigherFactory {
public static BananaWeigher getInstance(Banana banana) {
return new BananaWeigher();
}
public static SpinachWeigher getInstance(Spinach spinach) {
return new SpinachWeigher();
}
}
public abstract class FactoryUser<P extends Plant> {
/**
* Method that knows how to call the appropriate WeigherFactory.getInstance method
* Defined by concrete implementations that know the full plant type.
*/
public abstract Weigher<P> getWeigher(P plant);
// common code for all FactoryUsers
public void run(P plant) {
Weigher<P> weigher = getWeigher(plant);
int weight = weigher.weigh(plant);
System.out.println("the plant weighs: " + weight);
}
}
public class Main {
public static void main(String[] args) {
Banana banana = new Banana();
FactoryUser<Banana> factoryUser = new FactoryUser<Banana>() {
// We know the generic type to be Banana now, so we can call the
// appropriate WeigherFactory.getInstance method.
// This technique is known as double dispatch
@Override
public BananaWeigher getWeigher(Banana banana) {
return WeigherFactory.getInstance(banana);
}
};
factoryUser.run(banana);
}
}
When you don't know the actual type of the Plant (the Visitor Pattern)
We will need one PlantWeigher
that knows how to weigh all types of Plant
. It can delegate to specialised weighers if need be. But we need a central weigher as you will not be able to return a specific weigher and use it in a type-safe way. The central weigher will use the Visitor Pattern to dispatch the correct method for weighing each type of plant.
First you need a Visitor
interface and an implementation of that class that knows what to do with each concrete plant class. eg.
public interface Visitor<T> {
public T visit(Banana banana);
public T visit(Spinach spinach);
}
public class WeighingVisitor implements Visitor<Integer> {
@Override
public Integer visit(Banana banana) {
return banana.getBananaWeight();
}
@Override
public Integer visit(Spinach spinach) {
return spinach.getSpinachWeight();
}
}
public class PlantWeigher {
public int weigh(Plant plant) {
return plant.accept(new WeighingVisitor());
}
}
Secondly, you will need to modify the Plant
base class to define an abstract method that accepts a Visitor
. Only concrete implementations of Plant
need to implement this method.
public interface Plant {
public <T> T accept(Visitor<T> visitor);
}
public class Banana implements Fruit {
private int weight;
public Banana(int weight) {
this.weight = weight;
}
public int getBananaWeight() {
return weight;
}
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this); // the implementation is always exactly this
}
}
Finally, how to use all this:
List<Plant> plants = asList(new Banana(10), new Spinach(20));
PlantWeigher weigher = new PlantWeigher();
int weight = 0;
for (Plant p : plants) {
weight += weigher.weigh(p);
}
System.out.println("total weight: " + weight); // expect 30