Objective-C是否支持像Ruby一样的Mixin?

26

在 Ruby 中,有模块(Modules)的概念,你可以通过“混入”(mixing-in)模块来扩展一个类。

module MyModule
  def printone
    print "one" 
  end
end

class MyClass
  include MyModule
end

theOne = MyClass.new
theOne.printone 
>> one
在Objective-C中,我发现我有一组常用方法,我希望许多类都可以“继承”。除了创建一个共同的类并从该共同的类派生所有类之外,还有什么其他方法可以实现这一点?
在Objective-C中,我有一组常用的方法,想让多个类能够“继承”它们。除了创建一个共同的类并让所有类都继承它以外,还有哪些方法可以实现这个目的?
4个回答

31

不要介意我自己推销一下:ObjectiveMixin

它利用了Objective-C运行时在运行时向类添加方法的能力(与编译时仅限于categories的方式相反)。看看它,它的工作方式非常好,并且类似于Ruby的mixins。


4
在“你做不到”的众声中,你不仅提供了一个方法让你能够做到,还提供了一个解放。非常出色的工作。 - Slipp D. Thompson
天哪!我喜欢那个。我总是对我最喜欢的语言缺乏多重继承感到沮丧。 - MonsieurDart

28

编辑:因为一些人认为我应该为 Objective-C 的限制负责,所以添加了一些更改。

简短回答:你做不到。Objective-C 没有 Ruby mixins 的等效物。

略微长一点的回答:Objective-C 确实有一些类似的东西:协议(protocols)。协议(在一些其他语言中叫接口)是一种定义一组方法的方式,一个采纳了该协议的类就要实现这个协议里的方法。但是协议本身并不提供实现。这个限制阻止了使用协议作为 Ruby mixins 的准确等效物。

更长的回答:然而,Objective-C 运行时具有一个公开的 API,可以让你玩转语言的动态特性。当你跳出语言范围时,你可以有默认实现的协议(也称为 concrete protocols)。Vladimir 的答案展示了其中一种方法。此时,我认为你可以完全理解 Ruby mixins。

但是,我不确定是否建议这样做。在大多数情况下,其他模式都能胜任工作,而不需要与运行时玩游戏。例如,你可以有一个子对象来实现混入的方法(has-a 而不是 is-a)。虽然玩弄运行时很好,但它有两个缺点:

  • 你的代码变得不那么可读,因为需要读者了解比语言本身更多的内容。当然,你可以(也应该)给代码加注释,但记住任何必要的注释都可能被视为实现缺陷。

  • 你依赖于语言的 特定实现。尽管 Apple 平台远远是 Objective-C 最常见的平台,但不要忘记 Cocotron 或 GnuStep(或 Etoilé),它们具有不同的运行时,这些运行时可能与 Apple 在这方面并不兼容。

顺便说一下,我在下面声明,分类不能向类中添加状态(实例变量)。通过使用运行时 API,你可以消除这个限制。但这超出了本答案的范围。

长篇回答:

两个 Objective-C 特性看起来像是可能的选择:分类和协议。如果我正确理解了问题的话,分类并不是正确的选择,正确的特性是协议。

让我举个例子。假设你希望你的一堆类都能拥有一个叫做“唱歌”的特定能力。那么你就定义一个协议:

@protocol Singer
    - (void) sing;
@end

现在你可以这样声明你自己的任何类采用协议:

@interface Rectangle : Shape <Singer> {
    <snip>
@end

@interface Car : Vehicle <Singer> {
    <snip>
@end

通过声明采用协议,它们承诺实现sing方法。例如:

@implementation Rectangle

- (void) sing {
    [self flashInBrightColors];
}

@end

@implementation Car

- (void) sing {
    [self honk];
}

@end
然后你可以像这样使用那些类:
void choral(NSArray *choir) // the choir holds any kind of singer
{
    id<Singer> aSinger;
    for (aSinger in choir) {
        [aSinger sing];
    }
}
注意,数组中的歌手不需要有共同的超类。还要注意,一个类只能有一个超类,但可以采用多个协议。最后,请注意编译器执行类型检查。
事实上,协议机制是使用mixin模式的多继承。该多继承受到了严格限制,因为协议不能向类添加新的实例变量。协议仅描述了采用者必须实现的公共接口。与Ruby模块不同,它不包含实现。
这就是大部分内容。我们来谈谈分类。
分类不在尖括号中声明,而是在括号中声明。区别在于,分类可以为现有类定义扩展而无需将其作为子类化。甚至可以针对系统类进行此操作。您甚至可以使用分类来实现类似mixin的东西。通常作为NSObject的范畴使用,以至于它们被称为“非正式”协议。
这是非正式的,因为1-编译器不执行类型检查,2-实现协议方法是可选的。
今天不再需要将分类用作协议,特别是因为正式协议现在可以声明其中一些方法是可选的,关键字为@optional或必需(默认)为@required
分类仍然很有用,可以为现有类添加一些特定于领域的行为。 NSString是常见的目标。
还值得指出的是,大多数(如果不是全部)NSObject的设施实际上在一个NSObject协议中声明。这意味着没有必要将NSObject用作所有类的共同超类,尽管由于历史原因,人们仍然经常这样做......因为这样做没有任何缺点。但是某些系统类,例如NSProxy,不是NSObject

24
“协议只描述了采用者必须实现的公共接口。与 Ruby 模块不同,它不包含实现。” - 我认为整个重点在于有一些代码希望在各种类中重复使用而不需要重新实现它,因此使用协议在这里根本没有帮助... - Kuba Suder
8
这个回答完全错误。所有有关 Objective-C 的信息都是正确的,但问题问的是 Ruby mixins,这个回答实际上没有提供任何像 Ruby mixins 的东西。Ruby mixins 把现有方法的实现带到一个类中;不仅仅是声明。这里的例子给 Car 一个 sing 实现,它会 honk,并给 Rectangle 一个实现,它会 flash。使用 Ruby mixins,实现总是相同的,并且总是共享的。即使分类也做不到这一点,因为它们无法与其他类共享。@Vladimir 的回答是正确的。 - Tim Shadel
好的,你是对的。我在我的回答中指出了这一点。除非你认为我“完全错误”写道:“实际上,协议机制是用于mixin模式的多重继承。该多重继承受到严格限制,因为协议不能向类添加新的实例变量。协议仅描述采用者必须实现的公共接口。与Ruby模块不同,它不包含实现。”如果是这种情况,我希望你能启发我,并告诉我为什么“完全错误”。 - Jean-Denis Muys
非常有趣,但我必须投下反对票,因为您不能将实现与协议结合在一起,而这似乎是问题正在寻找的。当谷歌给我这个页面时,这正是我所期待的。 - Lance Nanek
好的,有两个人认为我错了,因为Objective-C没有Ruby mixins的等效物。我会编辑我的答案,让这一点非常明确。 - Jean-Denis Muys
显示剩余6条评论

11

你可以使用 #include 直接混入代码。然而这不被建议,也违背了 Objective-C 中所有的规则,但是它也能正常运作。

请不要在生产环境中这样做。

例如在以下文件中:

MixinModule.header (不应编译或复制到目标文件)

-(void)hello;

MixinModule.body (不应被编译或复制到目标文件中)

-(void)hello{
    NSLog(@"Hello");
}

在 mixin 类中:

@interface MixinTest : NSObject
#include "MixinModule.header"
@end

@implementation MixinTest
#include "MixinModule.body"
@end

用途案例:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]){
    @autoreleasepool {
        [[[MixinTest new] autorelease] hello];
    }
    return 0;
}

请不要在生产代码中这样做。


1
每个点赞的人都在想:“是啊,这很hacky...但也许我只能用一次...” - Elliot Chance

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接