将协议中的@optional选择器移植到Java接口

时间:2012-07-18 16:18:59

标签: java interface porting objective-c-protocol

所以,让我们说你有这个:

if ([foo respondsToSelector:@selector(bar:)]) {
   [foo bar:abc];
} else {
   [qux mux:abc];
}

bar:mux:都有副作用。

你如何将它移植到Java,在协议(接口)中没有@optional成员这样的东西?

我可以想到三种方式:

  1. C方式:向接口添加一个方法,该方法返回一个表示实现哪些方法且有效的位域。
  2. COM方式:修改接口,以便所有方法返回结果代码并检查E_NOTIMPL。使用params表示返回值。
  3. (我想象的是)Java方法:将每个接口方法标记为抛出UnsupportedOperationException并捕获它们以检查未实现的方法。
  4. 我是否错过了其他令人信服的选择?假设这个代码不经常调用,所以我们不需要优化性能,我认为3是最好的方法,因为它是可执行的。是否存在备选方案的论据?

3 个答案:

答案 0 :(得分:2)

MonoTouch为您的问题提供了完善的解决方案:使用Objective-C设计的代码在OO VM语言中感觉相当舒适。

他们通过将基类标记为abstract并将所有必需方法标记为virtual来解决它,并在C#中保留未标记的可选方法(或者更确切地说,标记为{{1}},但Java不需要那)。它明确地围绕所需方法的编译时强制执行,同时仍然允许可选方法。

更多细节:http://docs.xamarin.com/index.php?title=ios/tutorials/Events%2C_Protocols_and_Delegates#Protocols_Deep_Dive

答案 1 :(得分:1)

不,你描述为“Java方式”的东西实际上是一种反方式:在程序的常规流程中不应该捕获RuntimeException子类,因为它们表示编程错误。 / p>

更好的方法是将协议拆分为更小的部分:所有必需的方法最终都在一个接口中,可选的方法将在它们自己的微小接口中。现在,您可以使用instanceof测试对象,以确定是否实现了可选接口及其隐含方法。

以下是一个例子:

@protocol MyProtocol
@optional
    -(void)method1;
    -(void)method2;
@required
    -(void)method3;
    -(void)method4;
    -(void)method5;
@end

interface IMyProtocol {
    void method3();
    void method4();
    void method5();
}

interface IMyProtocolMethod1 {
    void method1();
}

interface IMyProtocolMethod2 {
    void method2();
}

class MyProtocolImplWithMethod2 implements IMyProtocol, IMyProtocolMethod2 {
    public void method2() {
    }
    public void method3() {
    }
    public void method4() {
    }
    public void method5() {
    }
}

然后你可以写这个支票:

if (obj instanceof IMyProtocolMethod2) {
    ((IMyProtocolMethod2)obj).method2();
}

答案 2 :(得分:1)

坦率地说,Java的方法是避免这种情况。通常,Java程序员会更加努力(并使用许多已建立的模式)而不是打破多态性 - 因此切换类型将被视为一种不好的做法。

最简单的解决方案和@optional的直接翻译将是使用instanceof并在接口内部使用可选方法来扩展所需的方法:

interface MyProtocol {
   void required1();
   void required2();
}

interface MyProtocolWithOptional extends MyProtocol {
   void optional1();
   void optional2();
}

然后:

public static void test(MyProtocol mp) {
   mp.required1();
   if (mp instanceof MyProtocolWithOptional) {
       ((MyProtocolWithOptional)mp).optional1();
   }
}

如果您有很多接口,并且您的应用程序在运行时是可扩展的,那么您可以使用类似功能模式(http://java.dzone.com/print/4771)的解决方案,其中可以查询每个对象的可用接口(这有点像COM ,有点不像COM)。你可以从以下内容开始:

interface ThingWithCapabilities<T> {
    T interfaceFor(Class<T> inteface)
}

ThingWithCapabilities是所有其他人扩展的基本接口。然后在运行时使用它:

public static void test(ThingWithCapabilities mp) {
    if (mp.interfaceFor(MyProtocolWithOptions.class) != null) {
        mp.interfaceFor(MyProtocolWithOptions.class).optional1();
    }
}

好消息是铸件消失了。更好的是你没有将MyProtocolWithOptions实例的生命周期与ThingWithCapabilities的生命周期联系起来(例如,你可以拥有一个MyProtocolWithOptions实例来满足许多ThingWithCapabilities实例;另一方面,你可以返回这个实例,如果你碰巧实现MyProtocolWithOptions)。如果你将它与null对象模式结合在一起(你有一个MyProtocolWithOptions的无状态实例,什么都不做,并且每个没有MyProtocolWithOptions功能而不是null的ThingWithCapabilities返回),你可以获得更好的代码:

public static void test(ThingWithCapabilities mp) {
    mp.interfaceFor(MyProtocolWithOptions.class).optional1();
}

除非你写的东西很大,否则这种方法可能很复杂。我刚刚提到它,因为它很有趣(我在一些宠物项目上成功使用过它)。