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

发布于 2024-08-25 13:24:45 字数 311 浏览 16 评论 0原文

在 Ruby 中,有模块,您可以通过“混合”模块来扩展类。

module MyModule
  def printone
    print "one" 
  end
end

class MyClass
  include MyModule
end

theOne = MyClass.new
theOne.printone 
>> one

在 Objective-C 中,我发现我有一组常用方法,我希望多个 Class 能够“继承”它们。在不创建公共类并从该公共类派生所有内容的情况下,我还可以通过哪些其他方法来实现此目的?

In Ruby, there's Modules and you can extend a class by "mixing-in" the module.

module MyModule
  def printone
    print "one" 
  end
end

class MyClass
  include MyModule
end

theOne = MyClass.new
theOne.printone 
>> one

In Objective-C, I find that I have a set of common methods that I want a number of Class to "inherit". What other ways can I achieve this without creating a common class and deriving all from that common class?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

诺曦 2024-09-01 13:24:46

这是我在 Objective-C 中实现 Mixins 的看法,而不直接使用 Objective-C 运行时。也许这对某人有帮助:https://stackoverflow.com/a/19661059/171933

This is my take on implementing Mixins in Objective-C, without using the Objective-C runtime directly. Maybe it's helpful to someone: https://stackoverflow.com/a/19661059/171933

等风也等你 2024-09-01 13:24:45

无耻插件:ObjectiveMixin

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

Shameless plug: ObjectiveMixin

It takes advantage of Objective-C runtime's capability of adding methods to a class in runtime (as opposed to categories, which are compile-time only). Check it out, it works pretty good and in a similar fashion to Ruby's mixins.

雪若未夕 2024-09-01 13:24:45

编辑:添加更改是因为有些人觉得我应对 Objective-C 的局限性负责。

简短回答:你不能。 Objective-C 没有相当于 Ruby mixins 的东西。

稍微简短一点的答案:Objective-C 确实有一些可以说具有相同风格的东西:协议。协议(某些其他语言中的接口)是一种定义一组方法的方法,该类采用该协议致力于实现。但协议不提供实现。该限制阻止将协议用作与 Ruby mixins 完全相同的协议。

甚至更短的答案:但是,Objective-C 运行时有一个公开的 API,可让您使用该语言的动态功能。然后你就可以跨出语言的范围,但你可以拥有带有默认实现的协议(也称为具体协议)。弗拉基米尔的回答展示了一种方法。到那时,在我看来,你可以得到 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

Edit: changes added because some people feel I am responsible for the limitations of Objective-C.

Short answer: you can't. Objective-C doesn't have the equivalent of Ruby mixins.

Slightly less short answer: Objective-C does have something with arguably the same flavour: protocols. Protocols (Interfaces in some other languages), are a way to define a set of methods an class that adopts that protocols is committing to implementing. A protocol doesn't provide an implementation though. That limitation prevents using protocols as an exact equivalent to Ruby mixins.

Even less short answer: However, the Objective-C runtime has an exposed API that lets you play with the dynamic features of the language. Then you step outside the language, but you can have protocols with default implementations (also called concrete protocols). Vladimir's answer shows one way to do that. At that point it seems to me you get Ruby mixins alright.

However, I am not sure I would recommend doing that. In most cases, other patterns fit the bill without playing games with the runtime. For example, you can have a sub-object that implement the mixed-in method (has-a instead of is-a). Playing with the runtime is OK, but has 2 drawbacks:

  • You make your code less readable as it requires readers to know a lot more than the language. Sure you can (and should) comment it, but remember that any necessary comment can be seen as an implementation defect.

  • You depend on that implementation of the language. Sure, Apple platforms are by far the most common ones for Objective-C but don't forget Cocotron or GnuStep (or Etoilé) which have different runtimes, which may or may not be compatible with Apple's on that respect.

As a side note, I state below that categories cannot add state (instance variables) to a class. By using the runtime API, you can lift that limitation too. This is beyond the scope of this answer however.

Long answer:

Two Objective-C features look like possible candidates: categories and protocols. Categories are not really the right choice here, if I understand the question properly. The right feature is a protocol.

Let me give an example. Suppose you want a bunch of your classes to have a specific ability called "sing". Then you define a protocol:

@protocol Singer
    - (void) sing;
@end

Now you can declare that any of your own classes adopts the protocol the following way:

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

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

By declaring that they adopt the protocol they commit themselves to implementing the sing method. For example:

@implementation Rectangle

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

@end

@implementation Car

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

@end

Then you use those classes for example like this:

void choral(NSArray *choir) // the choir holds any kind of singer
{
    id<Singer> aSinger;
    for (aSinger in choir) {
        [aSinger sing];
    }
}

Notice that the singers in the array don't need to have a common superclass. Notice also that a class can have only one superclass, but many adopted protocols. Notice finally that type checking is done by the compiler.

In effect, the protocol mechanism is multiple inheritance used for the mixin pattern. That multiple inheritance is severely limited because a protocol cannot add new instance variables to a class. A protocol only describes a public interface adopters must implement. Unlike Ruby modules it does not contain an implementation.

That's the most of it. Let's mention categories however.

A category is declared not in angle brackets, but between parenthesis. The difference is that a category can be defined for an existing class to expand it without subclassing it. You can even do so for a system class. As you can imagine, it's possible to use categories to implement something similar to mixin. And they were used that way for a long time usually as category to NSObject (the typical root of the inheritance hierarchy), to such an extent that they were called "informal" protocols.

It's informal because 1- no type checking is done by the compiler, and 2- implementing the protocol methods is optional.

There is no need today to use categories as protocols, especially because the formal protocols can now declare that some of their methods are optional with the keyword @optional or required (the default) with @required.

Categories are still useful to add some domain specific behavior to an existing class. NSString is a common target for that.

It's also interesting to point out that most (if not all) of NSObject facilities are in fact declared in a NSObject protocol. This means that it's not really compelling to use NSObject as a common superclass for all classes, though this is still commonly done for historical reasons, and well... because there is no drawback for doing so. But some system classes, such as NSProxy, are not NSObject.

蹲在坟头点根烟 2024-09-01 13:24:45

您实际上可以使用#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;
}

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

You can literally mixin the code using #include. This is not advisable and is against all the religions in objective-c, however works perfectly.

Please, don't do it in the production code.

for example in the file:

MixinModule.header (should not be compiled or copied to the target)

-(void)hello;

MixinModule.body (should not be compiled or copied to the target)

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

in mixin class:

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

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

usage case:

#import <Foundation/Foundation.h>

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

Please, don't do it in the production code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文