Java多类型方法参数?

Java多类型方法参数?,java,design-patterns,code-duplication,Java,Design Patterns,Code Duplication,我想知道是否可以要求java方法参数是有限类型集中的任何类型。例如,我正在使用一个库,其中两个(或更多)类型具有公共方法,但它们在类型层次结构中的最低公共祖先是Object。我这里的意思是: public interface A { void myMethod(); } public interface B { void myMethod(); } ... public void useMyMethod(A a) { // co

我想知道是否可以要求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();
   }
class Wrapper1 implements MyInterface {

    private final Type1 type1;

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

    @Override
    public void myMethod() {
        type1.method1();
    }
}
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));   
}
java中已经有类似类型的语法。例如:

  try{
     //fail
  } catch (IllegalArgumentException | IllegalStateException e){
     // use e safely here
  }
显然这是不可能的。如何使用这种不可编辑的类型层次结构实现设计良好的代码?

尝试使用设计模式

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

public interface Base {
    void myMethod();
}

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

此外,您还可以使用类似于的工具您可以使用单个方法
myMethod
编写
接口
MyInterface
。然后,对于每个类型,你都要考虑作为有限集的一部分,写一个包装类,像这样:

   public void useMyMethod(A|B obj){
      obj.myMethod();
   }
class Wrapper1 implements MyInterface {

    private final Type1 type1;

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

    @Override
    public void myMethod() {
        type1.method1();
    }
}
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));   
}
然后,您只需要使用
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

,那么建模需求的正确方法是在a和B都扩展的超类型接口C中声明myMethod();然后,您的方法接受类型C作为其参数。在您描述的情况下,您很难做到这一点,这表明您没有以实际反映类行为的方式对类层次结构进行建模

当然,如果你不能改变界面结构,你可以通过反射来改变

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

正确的方法是使用Java泛型


请参见

如何将函数作为参数传递给useMyMethod函数

如果您使用的是Java<8:

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

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

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

这可能不构成最佳实践,但您能否创建一个新类(称之为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的方法中保留任何不重复的代码(如果有)。

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

这将是新的界面:

public interface Base {
    void myMethod();
}

public interface A extends Base {}
public interface B extends Base {}
...
public void useMyMethod(Base b) {
    b.myMethod()
}
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|B obj){
      obj.myMethod();
   }
class Wrapper1 implements MyInterface {

    private final Type1 type1;

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

    @Override
    public void myMethod() {
        type1.method1();
    }
}
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));   
}
请注意,为了简化问题,您甚至可以选择一个现有接口(
A
B
)作为公共接口


(请看另一个例子,以及与本主题相关的其他想法)

由于您没有公共接口,编译器应该如何检查两个或更多类是否具有公共“接口”?问题还在于,根据程序代码,您定义为“复制”的代码是不重复的,因为您有两个不同的类层次结构-代码看起来相同,但工作在完全不同的数据结构上。这在Scala中很容易,但可能对您没有帮助。我所能建议的就是为实现另一个接口的一个接口制作一个包装器(或者为实现同一接口的两个接口制作包装器)。但这需要为每种类型编写一个包装器。如果不这样做,恐怕就不会有一个静态类型安全的解决方案。至少,如果有,我会感到惊讶。嘿@egelev,你觉得这些答案有用吗?如果是这样,请给作者一些荣誉,并标记为接受一个!谢谢。您可能错过了“如何使用这种不可编辑的类型层次结构实现设计良好的代码?”@Smutje,您不需要编辑(原始)类型层次结构。这就是为什么它被称为
包装器
。您可能应该添加第二个代码,该包装器实际上是如何与两个对象methodCall一起使用的(新包装器(t1);)-我甚至会考虑一个包含两个构造函数的包装器类,每个构造函数一个interface@Falco很好。我已经按照你的建议改进了我的答案,除了我选择了静态工厂而不是多个构造函数。这是我最喜欢的答案。这只是实现其他答案中提到的适配器模式的另一种方法,使用委托模式实现适配器本身。但是,它在代码方面有相当大的开销,因为您需要定义新的公共接口和两个委托类。因此,只有当您有大量重复的代码时,这才可能是值得的。您可能错过了“如何使用这种不可编辑的类型层次结构实现设计良好的代码?”这一部分,因为Base没有所需的方法。我仍然看不到模式。更清楚。@5gon12eder,我认为
适配器
是众所周知的模式,可能很容易实现