让我们说我班上有两个构造函数:
public User (List<Source1> source){
...
}
public User (List<Source2> source) {
...
}
让我们说这两个构造函数都提供了有关用户的相同信息,并且是为不同用例构建用户的同等有效方法。
在Java中,由于类型擦除,你不能这样做 - Java不会接受两个具有参数List&lt;的构造函数。 ? &GT;
那么,解决这个问题的方法是什么?什么是解决方案不是过度杀伤但仍然尊重基本的OO?由于Java没有强大的泛型支持,因此必须构建一个工厂方法或其他接口似乎是错误的。
以下是我能想到的可能性:
1)接受List<?>
作为构造函数的参数,并在构造函数中解析您需要哪种逻辑,或者如果它不是任何可接受的类型则抛出异常。
2)创建一个接受List的类,构造相应的User对象,然后返回它。
3)围绕List<Source1>
和List<Source2>
创建可以传递给User构造函数的包装器。
4)将这个人子类化为两个类,除了构造函数之外,所有的功能都是继承的。一个的构造函数接受Source1,另一个接受Source2。
5)用一个构建器包装这个人,其中两个不同的构建器方法用于两个不同的数据源进行实例化。
我的问题是这些:
1)是否需要使用Java或有意的设计决策来解决这个问题?什么是直觉?
2)在保持良好代码而不引入不必要的复杂性方面,哪种解决方案最强?为什么呢?
这个问题类似:Designing constructors around type erasure in Java但没有详细说明,它只是提出了各种解决方法。
答案 0 :(得分:8)
通常的做法是使用factory methods:
public static User createFromSource1(List<Source1> source) {
User user = new User();
// build your User object knowing you have Source1 data
return user;
}
public static User createFromSource2(List<Source2> source) {
User user = new User();
// build your User object knowing you have Source2 data
return user;
}
如果仅想要使用Source1
或Source2
进行构建(即您没有默认构造函数),则只需隐藏构造函数,强制客户端使用工厂方法:
private User () {
// Hide the constructor
}
出现这个问题是因为你不能以不同的方式命名构造函数,如果这些是正常的方法,那就是你如何克服它。因为构造函数名称被固定为类名,所以这种代码模式只能区分,然后给出相同类型的擦除。
答案 1 :(得分:1)
1:保持与擦除的向后兼容性。
2:你的班级可以使用泛型吗?像这样:
public class User<T> {
private List<T> whatever;
public User(List<T> source){
....
}
}
我不确定这是否是你所指的(2)
答案 2 :(得分:1)
基本问题是在泛型存在之前设计了语言(构造函数名称是固定的),因此它无法处理由于类型擦除而导致的冲突,这将通过重命名方法进行规范处理区分它们。
一个“解决方法”,不依赖于工厂方法,是添加另一个非类型参数,以使编译器能够区分它们:
public User(List<Source1>, Source1 instance) {
// ignore instance
}
public User(List<Source2>, Source2 instance) {
// ignore instance
}
虽然这有点蹩脚,因为你可以用任何东西替换那些额外的参数(例如Integer
和String
,或者只是让其中一个省略第二个参数),它仍然可以工作。此外,忽略额外参数 - 它仅用于区分构造函数。尽管如此,它确实允许代码在不添加任何额外方法或特殊代码的情况下工作。
答案 3 :(得分:0)
public class User<T> {
public User(List<T> source){
}
}
或者可能更好:
public class User<T extends SomeTypeOfYours> {
public User(List<T> source){
}
}
Wheer SomeTypeOfYours
是Source1
和Source2
的超类型。
答案 4 :(得分:0)
我喜欢一般的工厂理念,或者对User进行泛化(如@Markus Mikkolainen所建议的那样),但一种可能的替代方法是将列表的类作为第二个参数传递并切换它,例如。
public User<List<?> source, Class<?> clazz) {
switch(clazz) {
case Source1.class: initForSource1(); break;
case Source2.class: initForSource2(); break;
default: throw new IllegalArgumentException();
}
}
如果有一些共同的祖先类,<?>
可能是其他东西。我可以想象很多情况下这是一个糟糕的想法,但有些可能是可以接受的。