我正在编写一个方法,该方法应该接受两个类型之一的对象作为其参数,这两个类型不共享除Object之外的父类型。例如,类型是梦想和大蒜。您可以同时执行dreams.crush()
和garlic.crush()
。我希望有一个方法utterlyDestroy(parameter)
,它可以接受Dreams和Garlic作为参数。
utterlyDestroy(parameter) {
parameter.crush()
}
Garlic和dream都是某个库的一部分,所以让它们实现一个接口ICrushable(这样我就可以编写utterlyDestroy(ICrushable parameter)
)不是一个选项。
我的方法体很长,所以重载就意味着重复代码。丑陋。 我相信我可以使用反射并做一些类黑客攻击。难看。
我尝试使用泛型,但显然我不能写像
这样的东西utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)
是否有可能将大蒜强加给梦想?
utterlyDestroy(Object parameter) {
((Dreams)parameter).crush()
}
但这仍然是丑陋的。我的其他选择是什么?处理这种情况的首选方法是什么?
答案 0 :(得分:29)
这个怎么样:
interface ICrushable {
void crush();
}
utterlyDestroy(ICrushable parameter) {
// Very long crushing process goes here
parameter.crush()
}
utterlyDestroy(Dreams parameter) {
utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}
utterlyDestroy(Garlic parameter) {
utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}
新开发应该实现ICrushable接口,但是对于现有的Classes,参数包含在ICrushable中并传递给完成所有工作的utterlyDestroy(ICrushable)。
答案 1 :(得分:6)
这么简单的事情怎么样?
utterlyDestroy(Object parameter) {
if(parameter instanceOf Dreams){
Dream dream = (Dreams)parameter;
dream.crush();
//Here you can use a Dream
}
else if(parameter instanceOf Garlic){
Garlic garlic = (Garlic)parameter;
//Here you can use a Garlic
garlic.crush();
}
}
如果utterlyDestroy
太复杂而且你只想打电话给crush
那么这就是你想要的
答案 2 :(得分:4)
您可以在Java中实现Haskell-esque Either-class;像这样的东西:
class Either<L,R>
{
private Object value;
public static enum Side {LEFT, RIGHT}
public Either(L left) {value = left;}
public Either(R right) {value = right;}
public Side getSide() {return value instanceof L ? Side.LEFT : Side.RIGHT;}
// Both return null if the correct side isn't contained.
public L getLeft() {return value instanceof L ? (L) value : null;}
public R getRight() {return value instanceof R ? (R) value : null;}
}
然后你让那个方法采用Either<Dreams, Garlic>
类型的东西。
答案 3 :(得分:2)
只需使用方法重载。
public void utterlyDestroy(Dreams parameter) {
parameter.crush();
}
public void utterlyDestroy(Garlic parameter) {
parameter.crush();
}
如果您想以相同的方式支持这两种类型,可以为它们定义一个通用接口并使用泛型。
答案 4 :(得分:2)
您可以使用界面和adapt类型。
接口:
public interface Crushable {
public void crush();
}
示例调用:
public class Crusher {
public static void crush(Crushable crushable) {
crushable.crush();
}
}
示例适配器工厂方法:
public final class Dreams {
public static Crushable asCrushable(final Dream dream) {
class DreamCrusher implements Crushable {
@Override
public void crush() {
dream.crush();
}
}
return new DreamCrusher();
}
private Dreams() {}
}
消费者代码如下所示:
Dream dream = new Dream();
Crushable crushable = Dreams.asCrushable(dream);
Crusher.crush(crushable);
如果您有许多类型可以适应,您可以考虑反思。这是一个使用Proxy类型的(未优化的)适配器工厂:
public final class Crushables {
private static final Class<?>[] INTERFACES = { Crushable.class };
public static Crushable adapt(final Object crushable) {
class Handler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return crushable.getClass()
.getMethod(method.getName(), method.getParameterTypes())
.invoke(crushable, args);
}
}
ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
}
private Crushables() {}
}
对于API消费者来说,这并不难看:
Dream dream = new Dream();
Crushable crushable = Crushables.adapt(dream);
Crusher.crush(crushable);
然而,正如反射一样,你牺牲了编译时类型检查。
答案 5 :(得分:1)
创建界面Crushable似乎是最干净的方式。将Garlic或Dreams分类为选项,并将接口添加到子类型中吗?
除此之外,您可以将公共代码放在私有方法中,并且在调用公共代码之前,让两个版本的utterlyDestroy执行他们必须对单个对象执行的操作。如果你的方法体长,可能需要将其分解为私有方法。我猜你已经想到了这一点,因为它比添加一个接口更加明显。
您可以将参数作为Object引入,然后进行转换。这是你反思的意思吗?即,
public void utterlyCrush(Object crushable) {
if (crushable instanceOf Dream) {
...
}
if (curshable instanceOf Garlic) {
...
}
但是,如果一个人不是另一个人的子类型,那么从Garlic转向Dream不是一个选择。
答案 6 :(得分:1)
如果您要在项目的许多地方以相同的方式对待它们,我建议将它们包装在类中,例如适配器。
答案 7 :(得分:0)
我正在使用:
void fooFunction(Object o){
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code
}
但只有在Type2可以转换为Type1
时才有效