Поддерживает ли Objective-C Mixin, как Ruby?

В Ruby есть модули, и вы можете расширить класс, «подмешав» модуль.

module MyModule
  def printone
    print "one" 
  end
end

class MyClass
  include MyModule
end

theOne = MyClass.new
theOne.printone 
>> one

В Objective-C я обнаружил, что у меня есть набор общих методов, которые я хочу, чтобы ряд классов «наследовал». Какими другими способами я могу добиться этого, не создавая общий класс и не производя все от этого общего класса?


person hacksignal    schedule 21.03.2010    source источник


Ответы (4)


Изменить: изменения добавлены, потому что некоторые люди считают, что я несу ответственность за ограничения Objective-C.

Короткий ответ: нельзя. В Objective-C нет эквивалента примесей Ruby.

Чуть менее короткий ответ: в Objective-C есть что-то похожее: протоколы. Протоколы (интерфейсы на некоторых других языках) — это способ определить набор методов, которые класс, принимающий эти протоколы, обязуется реализовать. Однако протокол не обеспечивает реализацию. Это ограничение не позволяет использовать протоколы как точные эквиваленты примесей Ruby.

Еще менее краткий ответ: Однако среда выполнения Objective-C имеет открытый API, который позволяет вам экспериментировать с динамическими функциями языка. Затем вы выходите за пределы языка, но можете иметь протоколы с реализациями по умолчанию (также называемые конкретными протоколами). Ответ Владимира показывает один из способов сделать это. В этот момент мне кажется, что вы хорошо понимаете миксины Ruby.

Однако я не уверен, что рекомендовал бы это делать. В большинстве случаев другие шаблоны удовлетворяют всем требованиям, не играя в игры со средой выполнения. Например, у вас может быть подобъект, реализующий смешанный метод (имеет-а вместо является-а). Играть со средой выполнения можно, но есть 2 недостатка:

  • Вы делаете свой код менее читаемым, так как он требует от читателей знания гораздо большего, чем язык. Конечно, вы можете (и должны) прокомментировать его, но помните, что любой необходимый комментарий может рассматриваться как дефект реализации.

  • Вы зависите от этой реализации языка. Конечно, платформы 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];
    }
}

Обратите внимание, что певцы в массиве не обязательно должны иметь общий суперкласс. Заметьте также, что у класса может быть только один суперкласс, но много принятых протоколов. Наконец, обратите внимание, что проверка типов выполняется компилятором.

По сути, механизм протокола представляет собой множественное наследование, используемое для шаблона миксина. Это множественное наследование сильно ограничено, поскольку протокол не может добавлять в класс новые переменные экземпляра. Протокол описывает только общедоступный интерфейс, который должны реализовать пользователи. В отличие от модулей Ruby, он не содержит реализации.

Это самое главное. Однако давайте упомянем категории.

Категория объявляется не в угловых скобках, а между скобками. Разница в том, что для существующего класса можно определить категорию, чтобы расширить его, не создавая подклассов. Вы даже можете сделать это для системного класса. Как вы понимаете, можно использовать категории для реализации чего-то похожего на миксин. И так они долгое время использовались обычно как категория к NSObject (типичный корень иерархии наследования), до такой степени, что их называли "неформальными" протоколами.

Это неформально, потому что 1- компилятор не выполняет проверку типов и 2- реализация методов протокола не является обязательной.

Сегодня нет необходимости использовать категории в качестве протоколов, особенно потому, что формальные протоколы теперь могут объявлять некоторые из своих методов необязательными с ключевым словом @optional или обязательными (по умолчанию) с @required.

Категории по-прежнему полезны для добавления определенного поведения в существующий класс. NSString является общей целью для этого.

Также интересно отметить, что большинство (если не все) NSObject средств на самом деле объявлены в NSObject протоколе. Это означает, что на самом деле не обязательно использовать NSObject в качестве общего суперкласса для всех классов, хотя это все еще обычно делается по историческим причинам, и... потому что в этом нет никаких недостатков. Но некоторые системные классы, такие как NSProxy, не NSObject.

person Jean-Denis Muys    schedule 21.03.2010
comment
@jdmuys: этот ответ очень полный, но упущена крошечная деталь. Я надеюсь, что это нормально для вас, я взял на себя смелость добавить это. - person Johannes Rudolph; 21.03.2010
comment
черт! Параллельно редактировал. Я боюсь, что ваше редактирование могло быть потеряно. Вы можете повторить это! :-) - person Jean-Denis Muys; 21.03.2010
comment
@jdmuys: Нет проблем. Вот как (дарвинистско-) оптимистичный параллелизм работает здесь на SO :-) - person Johannes Rudolph; 21.03.2010
comment
Протокол описывает только общедоступный интерфейс, который должны реализовать пользователи. В отличие от модулей Ruby, он не содержит реализации. - ну, я думаю, все дело было в том, что есть какой-то код, который хочется переиспользовать в разных классах, не переопределяя его, поэтому использование протоколов здесь вообще не помогает... - person Kuba Suder; 24.05.2010
comment
Это вопрос шаблона. Если вы хотите повторно использовать какой-либо сервис из нескольких иначе несвязанных клиентских классов, то правильным шаблоном является определение этого сервиса в его собственном классе, каждый клиентский класс имеет экземпляр классов сервисов в своих переменных. Протоколы предназначены не для случая, когда вы хотите вызвать службу, а для случая, когда вы хотите, чтобы вас вызывал класс более высокого порядка. В моем примере певцов вызывают всякий раз, когда мы хотим, чтобы они спели. Если вам нужны оба, то комбинация может работать. И да, в Obj-C было бы неплохо иметь конкретные протоколы. Еще нет. - person Jean-Denis Muys; 25.05.2010
comment
На самом деле этот ответ совершенно неверен. Вся информация о Objective-C верна, но вопрос касается миксинов Ruby, и этот ответ на самом деле не дает ничего похожего на миксины Ruby. Примеси Ruby привносят реализацию существующего метода в класс; не просто заявление. Пример здесь дает Car реализацию sing, которая honks и Rectangle реализацию, которая flashes. Реализации примесей Ruby всегда идентичны и всегда используются совместно. Даже категории не могут этого сделать, так как они не могут делиться с другими классами. @ Ответ Владимира правильный. - person Tim Shadel; 15.04.2012
comment
Что ж, вы правы. И я указал это в своем ответе. Если, возможно, вы не имели в виду, что я совершенно неправильно написал: по сути, механизм протокола представляет собой множественное наследование, используемое для шаблона миксина. Это множественное наследование сильно ограничено, поскольку протокол не может добавлять в класс новые переменные экземпляра. Протокол описывает только общедоступный интерфейс, который должны реализовать пользователи. В отличие от модулей Ruby, он не содержит реализации. В этом случае я был бы признателен, если бы вы просветили меня и сказали, почему это совершенно неправильно. - person Jean-Denis Muys; 15.04.2012
comment
Категории здесь не совсем правильный выбор, если я правильно понимаю вопрос. Правильная функция — это протокол. Это просто неправильно, и давать пример исходного кода, а затем использовать его в качестве принятого ответа, действительно вводит в заблуждение. Оговорки посередине недостаточно, и, кроме того, теперь есть реальные решения этого вопроса: решения, которые смешивают реализацию метода с существующим классом. Так что это хорошо отвечает на некоторые вопросы, но не на этот. - person Tim Shadel; 16.04.2012
comment
Итак, я понимаю, что мой совершенно неправильный ответ не был таким уж неправильным в конце концов ... В любом случае, решение, предложенное Владимиром, играет в игру с системой времени выполнения, которая умна, но имеет существенный недостаток: вам нужно отключить тип компилятора проверка приведением, а коллизиями нужно управлять вручную. Поэтому я остаюсь при своем ответе: Objective-C - язык - не имеет лучших возможностей микширования, чем то, что я описал, в какие бы игры вы ни играли с одной реализацией. - person Jean-Denis Muys; 16.04.2012
comment
Очень интересно, но мне пришлось проголосовать против, потому что вы не можете включить реализацию в протокол, и, похоже, это то, что ищет вопрос. Черт возьми, это то, что я искал, когда Google дал мне эту страницу. - person Lance Nanek; 28.12.2012
comment
Хорошо, это 2 человека, которые считают, что я не прав, потому что Objective-C не имеет эквивалента примесей Ruby. Я отредактирую свой ответ, чтобы сделать его абсолютно ясным заранее. - person Jean-Denis Muys; 01.01.2013

Бессовестный плагин: ObjectiveMixin

Он использует возможности среды выполнения Objective-C для добавления методов в класс во время выполнения (в отличие от категорий, которые доступны только во время компиляции). Проверьте это, это работает довольно хорошо и аналогично миксинам Ruby.

person Vladimir Mitrovic    schedule 12.04.2011
comment
В толпе ответов "Вы не можете" вы предоставляете не только способ, которым вы можете, но и библиотеку. Превосходная работа. - person Slipp D. Thompson; 19.06.2013
comment
Священная корова! Я люблю это. Меня всегда расстраивает отсутствие множественного наследования моего любимого языка. - person MonsieurDart; 03.04.2014

Вы можете буквально смешать код, используя #include. Это не рекомендуется и противоречит всем религиям в Objective-C, однако работает отлично.

Пожалуйста, не делайте этого в производственном коде.

например, в файле:

MixinModule.header (не следует компилировать или копировать в цель)

-(void)hello;

MixinModule.body (не следует компилировать или копировать в цель)

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

в классе миксинов:

@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;
}

Пожалуйста, не делайте этого в рабочем коде.

person Radif Sharafullin    schedule 20.07.2012
comment
Все, кто проголосовал за это, думают: «Да, это супер хакерство… но, может быть, я могу просто использовать это один раз… - person Elliot Chance; 05.05.2016

Это мой взгляд на реализацию миксинов в Objective-C без прямого использования среды выполнения Objective-C. Возможно, это кому-то будет полезно: https://stackoverflow.com/a/19661059/171933

person Johannes Fahrenkrug    schedule 23.12.2013