[英]Java: Generics in interface and factory
我有一个基类Plant
,有两个子类型路径。 Fruit extends Plant
和Vegetable extends Plant.
Fruit
和Vegetable
也是抽象类,然后他们进一步有其他类,如Banana extends Fruit
和Spinach extends Vegetable
。
现在举个例子,一个操作必须通过这些类进行操作,并且有一个操作接口。 目前的设计如下:
我有一个界面:
abstract class Plant {}
abstract class Fruit extends Plant {}
class Banana extends Fruit {}
public interface PlantWeigher<T extends Plant> {
public int weight(T fruit);
}
然后我有:
public abstract class FruitWeigher<Y extends Fruit> implements PlantWeigher<Y> { }
然后我进一步具体化了:
public class BananaWeigher extends FruitWeigher<Banana> {
public int weight(Banana fruit);
}
现在,作为刚刚了解Plant的用户,我想获得Weigher的正确实例,然后执行操作。
工厂如下:
public class WeigherFactory {
public static PlantWeigher<? extends Plant> getInstance(Plant plant){
// based on some logic on Plant argument, I return, lets say:
return new BananaWeigher();
}
}
所以在用户类中,我调用以下函数:
PlantWeigher<? extends Plant> instance = WeigherFactory.getInstance(userPlant);
instance.weight(plant); // THIS LINE GIVES AN ERROR
有人可以帮我理解这里的设计有什么问题,也可以请一些人建议解决方案。
谢谢。
您的错误可能类似于The method weight(capture#2-of ? extends Plant) in the type PlantWeigher<capture#2-of ? extends Plant> is not applicable for the arguments (Banana)
The method weight(capture#2-of ? extends Plant) in the type PlantWeigher<capture#2-of ? extends Plant> is not applicable for the arguments (Banana)
。
你不能使用带有PlantWeigher<? extends Plant>
的Banana
PlantWeigher<? extends Plant>
PlantWeigher<? extends Plant>
有一个方法int weight(T fruit)
。 PlantWeigher<? extends Plant>
PlantWeigher<? extends Plant>
实际上可能是一个PlantWeigher<? extends Orange>
PlantWeigher<? extends Orange>
,大概不能称Banana
s。
您最简单的解决方案是将工厂方法更改为:
public static <T extends Plant> PlantWeigher<T> getInstance(T plant) {
并像这样使用它:
PlantWeigher<Plant> instance = WeigherFactory.getInstance(myBanana);
instance.weight(myBanana); // No more error
这完全是关于PECS原则的 。 我建议你阅读Java教程>通配符使用和Java泛型 指南 :什么是PECS 。
一般来说:
? extends X
? extends X
† ? super X
? super X
‡ 通常,您不能使用带有类型参数类型的参数的方法使用上限通配符类型参数( extends
)。 在这种情况下,您可以发送到instance.weight
的唯一有效值为null
。
instance.weight(null); // No more error
使用带有上限(extends)边界的通配符调用的类型参数是“out”变量。 这是因为编译器最多可以保证它是绑定的某个子类型。 在这种情况下,绑定是Plant
。 因此,您可以从同一个PlantWeigher<? extends Plant>
上的方法T getTestPortion()
获得 Banana
PlantWeigher<? extends Plant>
PlantWeigher<? extends Plant>
,只要它最初设置为PlantWeigher<Banana>
。
如果你使用一个较低的有界通配符,那么编译器可以保证类型参数是一些超级类型的Plant
,因此它可以称重Banana
或Orange
。
不幸的是, PlantWeigher<Banana>
和PlantWeigher<Orange>
不共享您可以使用的一些常见子类型 - 但这本身就表明您的设计存在问题。
† 对于方法返回变量的类型
‡ 有关方法参数的类型
这条线:
PlantWeigher<? extends Plant> instance = WeigherFactory.getInstance(userPlant);
意思是,“我有一个可以加重Plant
一些子类的重物,而不是所有种类的植物”。 例如,如果工厂将返回BananaWeigher
,尝试用此称重Tomato
是没有意义的。 要实现您想要的,您应该按如下方式更改工厂:
public class WeigherFactory{
public static <P extends Plant> PlantWeigher<P> getInstance(P plant){
// should return a correct Weigher for the subttype of Plant P
return ...
}
}
然后调用代码将返回如下:
PlantWeigher<Banana> instance =WeigherFactory.getInstance(banana);
instance.weigh(banana)
现在您有一个特定的PlantWeigher
,您可以使用特定类型调用它
或者 ,如果你想能够用任何种类的PlantWeigher
称重任何种类的Plant
,你应该改变后者:
public interface PlantWeigher {
public int weight(Plant p);
}
这确保了PlantWeigher
可以对任何计划进行加权,无论是否有感觉。
问题在于您使用泛型的代码的某些部分,而在其他部分则没有。 您可以采用不安全的方式,并使用一种工厂方法从一种方法返回不同类型的称重器。 但是,您将失去通过使您的称重器通用的类型安全性。 例如。
Plant banana = ...;
Plant spinach = ...;
Weigher<Plant> bananaWeigher = WeigherFactory.getInstance(banana);
bananaWeigher.weigh(spinach); // Compiles, but will produce a runtime error!
另一种方法是使WeigherFactory为每个具体的Plant
类具有不同的方法,并使得使用WeigherFactory
泛型的代码也是如此。 例如
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);
}
}
我们需要一PlantWeigher
知道如何称量所有类型Plant
PlantWeigher
。 如果需要,它可以委托给专业的称重者。 但是我们需要一个中央秤,因为您将无法返回特定的秤并以类型安全的方式使用它。 中央秤将使用访客模式为每种类型的工厂分配正确的方法。
首先,您需要一个Visitor
接口和该类的实现,该类知道如何处理每个具体的工厂类。 例如。
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());
}
}
其次,您需要修改Plant
基类以定义接受Visitor
的抽象方法。 只有Plant
具体实现需要实现此方法。
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
}
}
最后,如何使用这一切:
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.