【问题标题】:Method accepting two different types as parameter接受两种不同类型作为参数的方法
【发布时间】:2012-06-02 09:24:26
【问题描述】:

我正在编写一个方法,它应该接受两个类型之一的对象作为其参数,这两种类型的对象除了 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()
}

但这仍然很丑陋。我还有哪些其他选择?处理这种情况的首选方法是什么?

【问题讨论】:

    标签: java generics polymorphism overloading


    【解决方案1】:

    这个怎么样:

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

    【讨论】:

    • 呃,那天好像有人有一种破坏性的情绪。
    【解决方案2】:

    这么简单的事情怎么样?

    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,那么这就是您想要的。

    【讨论】:

    • 我自然想到了,但是使用 Object 作为参数似乎是错误的。
    • 你不是在重复同样的代码吗?对我来说没有意义。
    【解决方案3】:

    您可以在 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;}
    }
    

    然后让该方法采用Either&lt;Dreams, Garlic&gt; 类型的东西。

    【讨论】:

      【解决方案4】:

      你可以使用一个接口和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】:

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

        【讨论】:

        • 不,它只在一个地方。不过感谢您的回答。
        【解决方案6】:

        只需使用方法重载。

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

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

        【讨论】:

        • 正如我所说,我的方法很长,这意味着重复代码。
        • @JohnEye - 那么只在两个类中调用接口。如果您无法正确构建类层次结构,另请参阅 AadvarkSoup 的答案。
        【解决方案7】:

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

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

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

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

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

        【讨论】:

        • 不,通过反思,我的意思是使用一些漂亮的功能来找到一个名为 crush() 的方法并执行它或类似的东西。我写这些只是为了阻止“反射巨魔”回答。
        【解决方案8】:

        因为我正在使用:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-09-18
          • 2016-09-23
          • 2012-05-28
          • 1970-01-01
          • 2012-04-12
          • 2016-01-25
          • 1970-01-01
          相关资源
          最近更新 更多