编辑:因为一些人认为我应该为 Objective-C 的限制负责,所以添加了一些更改。
简短回答:你做不到。Objective-C 没有 Ruby mixins 的等效物。
略微长一点的回答:Objective-C 确实有一些类似的东西:协议(protocols)。协议(在一些其他语言中叫接口)是一种定义一组方法的方式,一个采纳了该协议的类就要实现这个协议里的方法。但是协议本身并不提供实现。这个限制阻止了使用协议作为 Ruby mixins 的准确等效物。
更长的回答:然而,Objective-C 运行时具有一个公开的 API,可以让你玩转语言的动态特性。当你跳出语言范围时,你可以有默认实现的协议(也称为 concrete protocols)。Vladimir 的答案展示了其中一种方法。此时,我认为你可以完全理解 Ruby mixins。
但是,我不确定是否建议这样做。在大多数情况下,其他模式都能胜任工作,而不需要与运行时玩游戏。例如,你可以有一个子对象来实现混入的方法(has-a 而不是 is-a)。虽然玩弄运行时很好,但它有两个缺点:
顺便说一下,我在下面声明,分类不能向类中添加状态(实例变量)。通过使用运行时 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
。