我想知道是否可以要求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
}
显然这是不可能的。如何使用这种类型的不可编辑类型层次结构来实现设计良好的代码?
答案 0 :(得分:15)
如何将函数作为参数传递给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;
}
});
}
对于Java&gt; = 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());
}
答案 1 :(得分:13)
尝试使用Adapter设计模式。
或者,如果可能的话,添加一些基本界面:
public interface Base {
void myMethod();
}
public interface A extends Base {}
public interface B extends Base {}
...
public void useMyMethod(Base b) {
b.myMethod()
}
此外,您可以使用与this
类似的内容答案 2 :(得分:8)
您可以使用单一方法interface
撰写MyInterface
myMethod
。然后,对于要作为有限集的一部分考虑的每种类型,编写一个包装类,如下所示:
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));
这种方法的优点是无论您使用哪种类型,代码都是相同的。如果您使用此方法,则必须将implements MyInterface
声明中的Wrapper1
替换为extends MyWrapper
。
答案 3 :(得分:5)
那么,建模你的需求的正确方法是在超类型接口C中声明myMethod(),A和B都扩展;然后,您的方法接受类型C作为其参数。在您描述的情况下,您无法执行此操作这一事实表明您没有以实际反映其行为方式的方式对类层次结构进行建模。
当然,如果你不能改变界面结构,那么你总是可以用反射来做。
public static void useMyMethod(Object classAorB) throws Exception {
classAorB.getClass().getMethod("myMethod").invoke(classAorB);
}
答案 4 :(得分:3)
这可能不是一个最佳实践,但是你可以创建一个新类(称之为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的方法中保留任何非重复的代码(如果有的话)。
答案 5 :(得分:2)
这在我看来很像模板模式:
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
}
}
答案 6 :(得分:2)
动态代理可用于在您定义的公共接口和实现符合到新接口的其他接口的对象之间创建桥接。然后,您可以让useMyMethod
将参数转换为新接口(作为动态代理),并使您的公共代码仅以新接口的形式编写。
这将是新界面:
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));
}
请注意,为简化问题,您甚至可以选择一个现有接口(A
或B
)作为公共接口。
(看看另一个例子here,以及围绕这个主题的其他想法)
答案 7 :(得分:0)
正确的方法是使用Java Generics。
请参阅http://docs.oracle.com/javase/tutorial/java/generics/bounded.html