37

我正在编写一个方法,该方法应该接受两种类型之一的对象作为其参数,这两种类型的对象除了 Object 之外不共享父类型。例如,类型是 Dreams 和 Garlic。你可以同时做dreams.crush()garlic.crush()。我想要一个方法utterlyDestroy(parameter),它可以接受 Dreams 和 Garlic 作为其参数。

utterlyDestroy(parameter) {
    parameter.crush()
}

Garlic 和 Dreams 都是某个库的一部分,因此让它们实现接口 ICrushable(以便我可以编写utterlyDestroy(ICrushable parameter))不是一种选择。

我的方法体很长,所以重载意味着重复代码。丑陋的。我确信我可以使用反射并进行一些类黑客攻击。丑陋的。

我尝试使用泛型,但显然我不能写类似

utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)

是否可以将 Garlic 类型转换为 Dreams?

utterlyDestroy(Object parameter) {
    ((Dreams)parameter).crush()
}

不过,这仍然很丑陋。我的其他选择是什么?处理这种情况的首选方法是什么?

4

8 回答 8

39

这个怎么样:

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 接口,但对于现有的类,参数包装在 ICrushable 中并传递给完成所有工作的 utterlyDestroy(ICrushable)。

于 2012-05-27T21:44:10.253 回答
10

像这样简单的事情怎么样?

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;
        garlic.crush();

        // Here you can use a Garlic

    }
}

如果utterlyDestroy太复杂和太大而你只想调用它,crush那么这就是你想要的。

于 2012-05-27T21:48:33.480 回答
6

您可以在 Java 中实现 Haskell 式的 Either 类;像这样的东西:

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;}
}

然后你让那个方法采用 type 的东西Either<Dreams, Garlic>

于 2012-05-27T21:39:09.323 回答
2

您可以使用接口并使您的类型适应它。

界面:

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);

但是,与反射一样,您牺牲了编译时类型检查。

于 2012-05-28T08:46:45.253 回答
1

创建 Interface Crushable 似乎是最干净的方法。子类型 Garlic 或 Dreams 是否是一个选项,并将您的界面添加到子类型?

除此之外,您可以将公共代码放在私有方法中,并让两个版本的 utterlyDestroy 在调用公共代码之前对各个对象执行它们必须做的事情。如果您的方法体很长,则可能无论如何都需要将其分解为私有方法。不过,我猜您已经想到了这一点,因为它比添加接口更为明显。

您可以将参数作为对象引入,然后进行强制转换。这就是你所说的反射吗?IE,

public void utterlyCrush(Object crushable) {
    if (crushable instanceOf Dream) {
         ...
    }
    if (curshable instanceOf Garlic) {
         ...
    }

但考虑到一个不是另一个的子类型,从 Garlic 转换为 Dream 不是一种选择。

于 2012-05-27T21:33:18.150 回答
1

只需使用方法重载。

public void utterlyDestroy(Dreams parameter) {
    parameter.crush();
}

public void utterlyDestroy(Garlic parameter) {
    parameter.crush();
}

如果您想以相同的方式支持多于这两种类型,您可以为它们都定义一个通用接口并使用泛型。

于 2012-05-27T21:33:23.703 回答
1

如果您打算在项目的许多地方以相同的方式对待它们,我建议将它们包装在一个类中,比如适配器。

于 2012-05-27T21:40:11.647 回答
1

当我使用:

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 时才有效

于 2014-04-08T14:42:08.457 回答