【问题标题】:Java multi-type method parameter?Java多类型方法参数?
【发布时间】:2015-02-14 16:23:08
【问题描述】:

我想知道是否可以要求 java 方法参数是有限类型集中的任何类型。例如 - 我正在使用一个库,其中两个(或更多)类型具有公共方法,但它们在类型层次结构中的最低共同祖先是 Object。我的意思是:

   public interface A {
      void myMethod();
   }

   public interface B {
      void myMethod();
   }
...
   public void useMyMethod(A a) {
      // code duplication
   }

   public void useMyMethod(B b) {
      // code duplication
   }

我想避免代码重复。我想到的是这样的:

   public void useMyMethod(A|B obj){
      obj.myMethod();
   }

java 中已经有类似的语法类型。例如:

  try{
     //fail
  } catch (IllegalArgumentException | IllegalStateException e){
     // use e safely here
  }

显然这是不可能的。如何使用这种不可编辑的类型层次结构来实现设计良好的代码?

【问题讨论】:

  • 由于您没有公共接口,您的编译器应该如何检查两个或多个类是否具有公共“接口”?问题还在于,根据程序代码,您定义为“重复”的代码不会重复,因为您有两个不同的类层次结构 - 代码看起来相同,但适用于完全不同的数据结构。
  • 这在 Scala 中很容易,但这可能对您没有帮助。我所能建议的只是为一个实现另一个接口的接口(或实现相同接口的两个接口)制作一个包装器。
  • 既然你已经将它标记为“设计模式”,那么适配器模式就会浮现在脑海中。但这需要您为每种类型编写一个包装器。如果不这样做,恐怕不会有一个静态类型安全的解决方案。至少,如果有的话,我会感到惊讶。
  • 嘿@egelev,您是否发现任何有用的答案?如果是这样,请给作者一些荣誉并将其标记为已接受!谢谢。

标签: java design-patterns code-duplication


【解决方案1】:

将函数作为参数传递给你的 useMyMethod 函数怎么样?

如果您使用的是 Java

public interface A {
    void myMethod();
}

public interface B {
    void myMethod();
}

public void useMyMethod(Callable<Void> myMethod) {
    try {
        myMethod.call();
    } catch(Exception e) {
        // handle exception of callable interface
    }
}

//Use

public void test() {
    interfaceA a = new ClassImplementingA();
    useMyMethod(new Callable<Void>() {
        public call() {
            a.myMethod();
            return null;
        }
    });

    interfaceB b = new ClassImplementingB();
    useMyMethod(new Callable<Void>() {
        public call() {
            b.myMethod();
            return null;
        }
    });
}

对于 Java >= 8,您可以使用 Lambda Expressions

public interface IMyMethod {
    void myMethod();
}

public void useMyMethod(IMyMethod theMethod) {
    theMethod.myMethod();
}

//Use

public void test() {
    interfaceA a = new ClassImplementingA();
    useMyMethod(() -> a.myMethod());

    interfaceB b = new ClassImplementingB();
    useMyMethod(() -> b.myMethod());
}

【讨论】:

  • 不错,但它迫使我们向调用者公开我们将在参数上调用的方法。更改实现(简单地调用另一个方法)可能会迫使我们去编辑函数的每次使用。而且如果我们想调用多个方法,很快就会变得乏味。
  • 嘿@5gon12ender,您的积分有效。使用适配器的好处是封装了对方法的调用,因此如果需要更改方法,则只需更改适配器类即可。另一方面,您需要为每个接口创建一个适配器,这可能比功能解决方案更加乏味。这实际上取决于代码的范围(是小类的内部代码还是广泛使用的函数?)以及更改实现的可能性有多大。
【解决方案2】:

尝试使用Adapter 设计模式。

或者,如果可能,添加一些基本接口:

public interface Base {
    void myMethod();
}

public interface A extends Base {}
public interface B extends Base {}
...
public void useMyMethod(Base b) {
    b.myMethod()
}

另外,你可以使用类似于this的东西

【讨论】:

  • 您可能错过了“如何使用这种不可编辑的类型层次结构实现设计良好的代码?”部分。
  • 不能如图所示工作,因为 Base 没有所需的方法。
  • 我仍然看不到模式。 pbabcdefp 's answer 让它更清晰。
  • @5gon12eder,我认为Adapter 是众所周知的模式,很容易被谷歌搜索到。我还添加了一个指向维基百科页面的链接。
  • 我认为Adapter + Delegate 是要走的路,就像@pbespechnyi 的回答一样。这样,无需修改现有的类型层次结构。
【解决方案3】:

您可以使用单个方法myMethod 编写interface MyInterface。然后,对于您想作为有限集的一部分考虑的每种类型,编写一个包装类,如下所示:

class Wrapper1 implements MyInterface {

    private final Type1 type1;

    Wrapper1(Type1 type1) {
        this.type1 = type1;
    }

    @Override
    public void myMethod() {
        type1.method1();
    }
}

那么您只需要使用MyInterface 而不是有限类型集合中的一个,并且始终会调用来自适当类型的适当方法。

请注意,要实际使用这些包装类来调用方法myMethod,您必须编写

myMethod(new Wrapper1(type1));

这会变得有点难看,因为您必须记住集合中每种类型的包装类的名称。出于这个原因,您可能更愿意将MyInterface 替换为一个抽象类,该类具有几个生成包装器类型的静态工厂。像这样:

abstract class MyWrapper {

    static MyWrapper of(Type1 type1) {
        return new Wrapper1(type1);
    }

    static MyWrapper of(Type2 type2) {
        return new Wrapper2(type2);
    }

    abstract void myMethod();
}

然后就可以使用代码调用方法了

myMethod(MyWrapper.of(type1));

这种方法的优点是无论你使用哪种类型,代码都是相同的。如果使用这种方法,则必须将 Wrapper1 声明中的 implements MyInterface 替换为 extends MyWrapper

【讨论】:

  • 您可能错过了“如何使用这种不可编辑的类型层次结构实现设计良好的代码?”部分。
  • @Smutje,您不需要编辑(原始)类型层次结构。这就是为什么这被称为Wrapper
  • 您可能应该添加第二个代码,该代码截断了这个包装器如何与两个对象一起使用 methodCall(new Wrapper( t1 );) - 我什至会考虑一个带有两个构造函数的 Wrapper-Class,每个接口一个
  • @Falco 好点。我已经按照你的建议改进了我的答案,除了我选择了静态工厂而不是多个构造函数。
  • 这是我最喜欢的答案。通过使用委托模式来实现适配器本身,这只是实现其他答案中提到的适配器模式的另一种方法。但是,它在代码方面有相当多的开销,因为您需要定义新的通用接口和两个委托类。所以只有当你有很多重复的代码时才值得这样做。
【解决方案4】:

好吧,为您的需求建模的正确方法是在 A 和 B 都扩展的超类型接口 C 中声明 myMethod();然后,您的方法接受类型 C 作为其参数。在您描述的情况下,您无法执行此操作这一事实表明您没有以实际反映它们行为方式的方式对类层次结构进行建模。

当然,如果你不能改变接口结构,那么你总是可以用反射来做的。

public static void useMyMethod(Object classAorB) throws Exception {
    classAorB.getClass().getMethod("myMethod").invoke(classAorB);
}

【讨论】:

  • 为了至少在调用者的站点上使这个静态类型安全,我会考虑制作接受 Object private 的方法并为每种类型提供重载的 public 方法得到支持。这些只是委托给您展示的方法。这确实需要一些代码重复,但只需要技术单行,不需要业务逻辑。
  • @5gon12eder 这正是我会采用的方法 - 确保类型安全并调用底层私有方法的公共方法!
【解决方案5】:

这可能不构成最佳实践,但您能否创建一个新类(称为 C),其中包含 A 和 B 中重复的部分,并创建一个采用 C 的新方法,让您的方法拿 A 和 B 做一个 C 实例并调用新方法?

这样你就有了

class C {
    // Stuff from both A and B
}

public void useMyMethod(A a) {
    // Make a C
    useMyMethod(c);
}

public void useMyMethod(B b) {
    // Make a C
    useMyMethod(c);
}

public void useMyMethod(C c) {
    // previously duplicated code
}

这还可以让您在 A 和 B 的方法中保留任何不重复的代码(如果有的话)。

【讨论】:

    【解决方案6】:

    这在我看来很像模板模式:

    public interface A {
    
        void myMethod();
    }
    
    public interface B {
    
        void myMethod();
    }
    
    public class C {
    
        private abstract class AorBCaller {
    
            abstract void myMethod();
    
        }
    
        public void useMyMethod(A a) {
            commonAndUseMyMethod(new AorBCaller() {
    
                @Override
                void myMethod() {
                    a.myMethod();
                }
            });
        }
    
        public void useMyMethod(B b) {
            commonAndUseMyMethod(new AorBCaller() {
    
                @Override
                void myMethod() {
                    b.myMethod();
                }
            });
        }
    
        private void commonAndUseMyMethod(AorBCaller aOrB) {
            // ... Loads of stuff.
            aOrB.myMethod();
            // ... Loads more stuff
        }
    }
    

    在 Java 8 中它更加简洁:

    public class C {
    
        // Expose an "A" form of the method.
        public void useMyMethod(A a) {
            commonAndUseMyMethod(() -> a.myMethod());
        }
    
        // And a "B" form.
        public void useMyMethod(B b) {
            commonAndUseMyMethod(() -> b.myMethod());
        }
    
        private void commonAndUseMyMethod(Runnable aOrB) {
            // ... Loads of stuff -- no longer duplicated.
            aOrB.run();
            // ... Loads more stuff
        }
    }
    

    【讨论】:

      【解决方案7】:

      动态代理可用于在您定义的通用接口和实现其他接口的对象之间建立桥梁,这些接口符合新接口。然后,您可以让您的useMyMethods 将参数转换为新接口(作为动态代理),并让您的通用代码仅根据新接口编写。

      这将是新界面:

      interface Common {
        void myMethod();
      }
      

      然后,使用此调用处理程序:

      class ForwardInvocationHandler implements InvocationHandler {
        private final Object wrapped;
        public ForwardInvocationHandler(Object wrapped) {
          this.wrapped = wrapped;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
          Method match = wrapped.getClass().getMethod(method.getName(), method.getParameterTypes());
          return match.invoke(wrapped, args);
        }
      }
      

      你可以有这样的方法:

      public void useMyMethod(A a) {
        useMyMethod(toCommon(a));
      }
      
      public void useMyMethod(B b) {
        useMyMethod(toCommon(b));
      }
      
      public void useMyMethod(Common common) {
        // ...
      }
      
      private Common toCommon(Object o) {
        return (Common)Proxy.newProxyInstance(
          Common.class.getClassLoader(), 
          new Class[] { Common.class }, 
          new ForwardInvocationHandler(o));   
      }
      

      请注意,为简化问题,您甚至可以选择现有接口之一(AB)作为通用接口。

      (看看另一个例子here,以及围绕这个主题的其他想法)

      【讨论】:

        【解决方案8】:

        正确的方法是使用 Java 泛型。

        http://docs.oracle.com/javase/tutorial/java/generics/bounded.html

        【讨论】:

        • 如何将泛型类型参数限制为有限的类型集?
        • 很明显你有一个基本接口或对象类型,比如类型“AorB”,它有一个方法定义或实现。所以你定义了一个公共接口 Functor{}。您可以将 T 多次绑定到一系列类型:
        • OP 无法编辑现有接口以使其扩展通用接口 - “如何使用这种类型的不可编辑的类型层次结构实现精心设计的代码?
        • 嗯,使用multiple-bound extends,还是可以的。
        • 我不使用Java,但查看documentation,似乎您可以强制泛型类型参数扩展多种类型(T extends A &amp; B)-但您不能强制它扩展多种类型中的一个
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-02
        相关资源
        最近更新 更多