Abstract
В этой статье описывается интересное расширение среды исполнения Objective-C.
Проект
Я делал эту работу на проекте Substrate. Substrate был сервером приложений, который мы разрабатывали для Fannie Mae на NextSTEP в 1996-97 годах.
Задача
Я работал над несколькими ключевыми частями сервера: модельно-управляемой средой исполнения, менеджером конфигурации, брокером запросов и т. д. Руководителем проекта был Джек Гринфилд, умнейший человек, который очень любит абстракции, шаблоны проектирования и т. д. Он любит цитировать фразу Самуэля Хана Все, что можете сделать вы, я могу сделать мета В английском языке - это игра слов, потому что "мета" рифмуется с "better" ("лучше"). Одна из идей, на которые он обратил мое внимание, были КНП или Категории на Протоколах.
Объясняю:
Протокол - это термин в Objective-C для обозначения интерфейса: абстрактного определения API. У протокола нет ни данных, ни фактических реализаций API. Протоколы реализуются классами.
Категория - это уникальная концепция Objective-C. Категория - это расширение класса. Она не может добавлять поля, но может добавлять методы. Допустим, у нас есть класс String. Мы можем добавить к нему такую категорию:
@implementation String(Russian)
- (String*) translateToRussian
{
// Translate this string to Russian and return the result
}
@end
Вы можете отправить сообщение translateToRussian
любой строке, как если бы это был обычный метод, объявленный самим классом.
Категория на протоколе похожа на обычную категорию, но вместо того, чтобы добавлять новые методы в класс, она связывает новые реализации методов с протоколом. Реализации метода COP должны быть выражены в терминах методов объявленных в том же протоколе. Если класс реализует этот протокол, он автоматически получает все методы, добавленные к нему через КНП. Например, предположим, что у нас есть протокол под названием NaturalLanguageString
.
@protocol NaturalLanguageString
- (String*) content;
@end
Добавляем к нему категорию с реализацией:
@cop NaturalLanguageString
- (String*) translateToRussian
{
String* string = [self content];
// Translate this string to Russian
return string;
}
@end
Теперь все классы реализующие протокол NaturalLanguageString
будут отвечать на сообщение translateToRussian
.
Проблема
КНП были хорошей идеей, но они не поддерживались Objective-C. Я взял на себя смелость реализовать эту концепцию.
Решение
Для начала, я разобрался как работает среда исполнения Objective-C. Её внутренности были недокументированы. Оказалось, что структура данных для каждого класса состояла в основном из словарей, где ключом был селектор метода (const char *), а значением был указатель на реализацию метода. Значит, чтобы добавить метод в класс, нужно было просто добавить в его словарь новую пару (селектор, реализация).
В моей реализации объявление КНП представляло собой класс (C
), который объявлял, что он реализует тот самый протокол (P
). Я написал функцию, которая брала этот класс во время выполнения, выясняла, какой протокол она реализует, находила все остальные классы, которые реализовывали этот протокол прямо или косвенно, и добавляла методы КНП к каждому из них. При этом я придерживался следующих правил:
- Если класс
X
был подклассом, прямо или косвенно, классаY
, а классY
реализовывал , прямо или косвенно протоколP
, то не было необходимости добавлять методыC
, поскольку они уже были унаследованы. - Если класс
X
поддерживал протоколP
, и в нем были реализации каких-то из методовC
, эти реализации не замененялись реализациями изC
. Это позволяло любому классу перопределить реализацию любого из методов КНП.
Для автоматической инициализации при запуске программы, каждая КНП была зарегистрирована в среде выполнения Objective-C.
Я реализовал несколько довольно обширных КНП на протоколе Collection: поиск, сортировка, даже выполнение поисковых запросов были автоматически доступны для всех коллекций в системе: массивов, множеств, пользовательских коллекций и т. д.
КНП оказались чрезвычайно мощным механизмом, и мы с Джеком были убеждены, что язык программирования Java выиграет от поддержки КНП. Мы обсуждали это с архитекторами Java, но они не хотели усложнять язык в тот момент. По иронии судьбы, позже они добавили в Java поддержку внутренних классов, обобщений и т. д., что сделало язык почти таким же сложным, как C ++. Думаю, от сложности все-таки не уйти. В свете этой идеи, возможно, время для КНП еще впереди.
[ОБНОВЛЕНИЕ, 2021-01-24: в наши дни "КНП" распространены повсеместно. Например, в Swift они называются реализациями по умолчанию; Java также приобрела их как методы по умолчанию в JDK 8]