Правильно ли я понимаю, что вы хотите чтобы инжектор сам разбирался, что подать на выход конструктора?
что передать ему в конструктор?
посмотреть определение конструктора
Скажу, что это все равно service locator.
Не надо давать имена, скажите чем плохо, чтобы я мог лучше продумать код. Запрос на DI поступил именно и только от Вас, поэтому я пытаюсь понять, чего Вы хотите. Если Ваши желания будут мне интересны — я их постараюсь реализовать.
Значит, это не DI.
как минимум, спецы из гугл, писавшие fruit, не разделяют Ваше мнение
«The basic idea behind a service locator is to have an object that knows how to get hold of ALL of the services that an application might need. „
Ну то есть как я сказал — объект имеет доступ ко ВСЕМ сервисам приложения. А я предлагаю нечто иное. я считаю важным ОБЯЗАТЕЛЬНО ограничивать доступ объекта теми сервисами, которые ему нужны.
Класс-потребитель не знает, как был создан объект, который он получил, и исходит из того, что там есть все сервисы, которые ему могут понадобиться.
Имхо, Вы используете неверное слово — не “ему могут понадобится», а «он использует». Засылать зависимости впрок — странно
Нет. Сервис-локатор плох тем, что он скрывает то, какие зависимости потребляет реализация.
А вот это интересно. Можно сказать, мы до самой сути дошли. Смысл моей статьи — неопределённая параметризация, то есть в Вашей терминологии — параметризация без без явного объявления зависимостей. Я в статье утверждаю, что это делает код проще, читабельнее, его написание — быстрее, и за счёт простоты и повышения читабельности упрощает отладку. Но важно, что я настаиваю на необходимости соответствия числа предоставляемых и потребляемых «модулей» или «сервисов» по-Вашему
Дело не в геттере (еще и потому, что «в норме» геттера в DI нет), дело в том, что потребитель не объявляет и не получает свои зависимости явно.
Это не так. Или не совсем так. В точке инжекта всегда будут видны зависимости. Если инжектор имеет узнаваемое имя, то их будет легко найти поиском. А вот если название инжекторов неединообразно, или в них нет узнаваемой и однозначно идентифицирующей части, а инжект (инициализация) запихнуты относительно рабочего кода неизвестно куда, тогда да, будет проблема. Потому что будут с изменением кода передаваться лишние зависимости, и в виду разных мест это будет сложно отследить.
з.ы. пожалуй, введу-ка я в следующий код для DI проверку на неиспользованные… в вашей терминологии — «сервисы». Будет однозначный контроль — не дашь нужный сервис, сработает ассерт. Дашь лишний — сработает ассерт. При этом будет печатать файл, строку и функцию, в которой сделан кривой инжект. и список избыточных зависимостей (или указание на ту зависимость, которой не хватило). Таким образом проблема сокрытия зависимостей, потребляемых реализацией, перестанет быть проблемой. Что скажете?
В моём представлении Service Locator — это штука, единая для всех классов. Если Вы создаёте объект для каждого класса индивидуально, со своим набором «сервисов» — то это уже параметризация.
.
Но не в терминологии дело. Вот у нас поменялось написание геттера. В чём негатив? Сервис-локатор, как я понимаю, плох тем, что он нарушает инкапсуляцию и модульность, давая доступ всем объектам ко всем сервисам. Но «локальный сервис-локатор» в вашей терминологии лишён этого недостатка. вы создаёте его перед инъекцией и включаете только то, что нужно. Так (на данный момент) вижу ситуацию я. А какие проблемы усматриваете Вы?
Только тогда поясните мне. Оставим пока вопрос о «знает про инфраструктуру DI». В чём принципиальная разница межу классическим примером DI
class Server {
public:
Server (
shared_ptr<RequestI> request_ptr,
shared_ptr<EventFilterI> event_ptr,
shared_ptr<vector<Listeners> > listeners_ptr)
: request_ ( request_ptr ),
event_ ( event_ptr ),
listeners_ ( listeners_ptr ) {
}
(...)
void OnEvent() {
const auto & listeners_ptr = get_listeners();
if ( listeners_ptr ) {
for ( const auto & the_listener : *listeners_ptr ) {
the_listener->OnEvent( event_data() );
}
}
(...)
};
и
class Server : public DynamicParametrization {
public:
typedef vector< ListenerI > ListenersList;
Server ( shared_ptr<TypedSet> dynamic_parametrization )
: DynamicParametrization ( dynamic_parametrization ) {
}
(...)
void OnEvent() {
const auto & listeners_ptr = Get< ListenersList >();
if ( listeners_ptr ) {
for ( const auto & the_listener : *listeners_ptr ) {
the_listener->OnEvent( event_data() );
}
}
(...)
};
Пока что я вижу, что второй пример вдвое лаконичее и вдвое читабельнее, и считаю, что если вместо get_listeners() написать Get < ListenersList '>() ничего не изменится. Но верю, что вы можете меня переубедить.
Возвращаемся к вопросу про «знает про инфраструктуру DI»
Как тогда вы относитесь к библиотеке для DI от google, — «fruit», где бы вам пришлось писать
Как видите, для fruit важно, чтобы класс знал об инфраструктуре DI (при этом тоже использует внешнюю инициализацию). Ваше мнение по этому поводу?
З.Ы. Пожалуйста, не используйте термин «локальный сервис-локатор». Он для меня звучит как «локальный синглтон».
З.З.Ы. Опять же, напоминаю, внешнюю инициализацию можно сделать и на TypedSet. Просто интересно понять, зачем.
Для меня естественным решением является передать TypedSet или StrategiesSet в конструктор. Предварительно их апгредовав под более удобное использование с паттерном DI.
Но многие стандартные библиотеки для DI используют внешнюю инициализацию. Инжектор аггрегирует, инкапсулирует или просто инициализирует произвольный класс. По вашему: есть ли в этом преимущество перед первым подходом?
во-первых, пример на java будет, думаю. Во-вторых, это моя первая статья на хабре(не считая песочницы) и я не ожидал холодного приёма. Полагал, что не вызову столь большого интереса)
«Более того, на практике hash_code() может меняться, например, после загрузки shared object'а, и один и тот же тип в коде в разных .so может иметь разный hash_code().»
Не наблюдал такого. clang и с O0, и с O3 и gcc на O3 просто херачит прямо runtime хэш имени класса, который задан в typeid info типа. Если тип известен на этапе компиляции — то автоматом загружает нужную typeid таблицу. Если динамическое определение — то из виртуальной таблицы ссылку на typeid таблицу берёт. А вот насчёт MS VC Не скажу — там всё упирается в функцию __std_type_info_hash, которую godbolt не показывает. но судя по расширению .so вы не о мелкософовском компиляторе…
Но компилятор можно и надурить. Если в указателе на невиртуальный предок записан адрес потомка. При указателе на невиртуальный класс компилер не будет разбираться и будет загружать typeid того типа, на который указывает формально указатель gcc.godbolt.org/z/TeNJsE. Тоже, полагаю, будет из shared_ptr — механизм тот же.
Спасибо, поставил плюсик. Не знал этого момента, больше полагался на то, как компилер в ассемблерный код это компилит. Впрочем, я всё равно собирался выложить «боевой код», т.е. код с меньшим числом упрощений в угоду читабельности. И там избавиться от typeid. Дело в том, что typeid на clang очень медленно работает, вызывая strlen и хэшируя название класса при каждом вызове. А это убивает всю идею. Смысл ведь в низкой цене и по скорости, и по числу строк. И заодно заменить std::map на std::array, что даст радикальное ускорение
А вообще, отсутствие гарантий и UB бывают чисто теоретическими. Пример — атомарность операций с выровненным int. Стандарт не гарантирует — а Intel гарантирует. И arm, кажется, тоже.
Или выход указателей за пределы выделенного куска памяти. По стандарту — это UB, но очень много людей на это полагается и строит, например, проверку выхода за пределы массива.
в этом коде тоже движением руки можно заменить маппер на std::unordered_map или QHash. Просто для примера с 2мя интерфейсами это неопрадванно. Но основная часть состоит не в log(n), а в последовательном strlen + хэшировании строки. Что касается хэша — при большом количестве данных он всё равно становится log(n). просто с хорошим понижающим коэффициентом. Хотя, если честно, я немного упростил. Для «боевой» системы нужно выбрасывать typeid, присваивать по запросу каждому классу номер (считая его один раз), и делать даже не хэшмап, а дек c кусками большого размера. грубо говоря, вы выделяете массив индексов заведомо больший, чем число типов у вас в использовании, но навсякий случай, чтобы избежать его переполнения, вместо array используете deque. Впрочем, и array сойдёт. Дело в том, что из-за неэффективной реализации typeid на современных компиляторах, получатся те же грабли, что и QHash[«ИмяМоегоИнтерфейса»], один в один. Эта дрянь не кэширует хэш (хотя его можно посчитать compile-time), поэтому он найдёт в typeid-структуре имя класса, сделает ему strlen, а потом захэширует. поэтому код в тексте статьи я попозже дополню более сложным «боевым», более оптимизированным, но менее понятным и читабельным.
Сожалею, что недостаточно чётко составил текст, из-за чего у Вас сложилось ложное представление о моих предложениях. Я не предлагаю паттерны — и не реализую их. Я предлагаю лишь маааленький инструментик — на 20 строк — который позволяет реализовывать некоторые паттерны ( в том числе и фаулеровский Service Locator — я правильно понял, вы его имели в виду под Service Provider? ), но не только. Стратегии тоже можно фигачить им. Важно, что в отличие от фаулеровского решения (которого придерживались мои коллеги на Qt) в предложенном инструменте минимум писанины и скорость доступа не 2*длина_строки*log(число_сервисов), а просто log(число_сервисов). Было бы конечно, круто придумать новый паттерн проектирования, но я изначально замахивался на нечто более скромное.
Причём здесь инструмент? сделайте два интерфейса, если работа с ним так различна. какие-нибудь SynchronousWork и AsynchronousWork. Инструмент позволяет сделать и так, и так, а как Вам надо — Вам виднее.
Точно нет. Я не предлагаю никаких фреймворков. Это как сравнить электросамокат с Бентли. Но по результатам комментов я сделал апдейт в тексте. Но в каком-то смысле вы правы — я предлагаю два класса, с помощью которых можно эффективно и дёшево (имхо) параметризовать другой класс, или реализовать Dependency Injection и Service Locator, или без лишней писанины добавить в класс «оптом» десяток стратегий.
Раз столько комментариев на тему переоткрытия мною велосипедов, значит, я недостаточно чётко сформулировал текст. Я не предлагаю паттернов проектирования вообще. Я предлагаю метод их реализации за нулевую стоимость и с минимумом писанины. Причём сам метод укладывается в 20 строк кода. Если кратко, то я предлагаю метод экономной параметризации класса (в смысле числа строк кода и скорости использования) чем угодно. И если вам интересно, я не миллениал и старше Вас.
посмотреть определение конструктора
Не надо давать имена, скажите чем плохо, чтобы я мог лучше продумать код. Запрос на DI поступил именно и только от Вас, поэтому я пытаюсь понять, чего Вы хотите. Если Ваши желания будут мне интересны — я их постараюсь реализовать.
как минимум, спецы из гугл, писавшие fruit, не разделяют Ваше мнение
Ну то есть как я сказал — объект имеет доступ ко ВСЕМ сервисам приложения. А я предлагаю нечто иное. я считаю важным ОБЯЗАТЕЛЬНО ограничивать доступ объекта теми сервисами, которые ему нужны.
Имхо, Вы используете неверное слово — не “ему могут понадобится», а «он использует». Засылать зависимости впрок — странно
А вот это интересно. Можно сказать, мы до самой сути дошли. Смысл моей статьи — неопределённая параметризация, то есть в Вашей терминологии — параметризация без без явного объявления зависимостей. Я в статье утверждаю, что это делает код проще, читабельнее, его написание — быстрее, и за счёт простоты и повышения читабельности упрощает отладку. Но важно, что я настаиваю на необходимости соответствия числа предоставляемых и потребляемых «модулей» или «сервисов» по-Вашему
Это не так. Или не совсем так. В точке инжекта всегда будут видны зависимости. Если инжектор имеет узнаваемое имя, то их будет легко найти поиском. А вот если название инжекторов неединообразно, или в них нет узнаваемой и однозначно идентифицирующей части, а инжект (инициализация) запихнуты относительно рабочего кода неизвестно куда, тогда да, будет проблема. Потому что будут с изменением кода передаваться лишние зависимости, и в виду разных мест это будет сложно отследить.
з.ы. пожалуй, введу-ка я в следующий код для DI проверку на неиспользованные… в вашей терминологии — «сервисы». Будет однозначный контроль — не дашь нужный сервис, сработает ассерт. Дашь лишний — сработает ассерт. При этом будет печатать файл, строку и функцию, в которой сделан кривой инжект. и список избыточных зависимостей (или указание на ту зависимость, которой не хватило). Таким образом проблема сокрытия зависимостей, потребляемых реализацией, перестанет быть проблемой. Что скажете?
.
Но не в терминологии дело. Вот у нас поменялось написание геттера. В чём негатив? Сервис-локатор, как я понимаю, плох тем, что он нарушает инкапсуляцию и модульность, давая доступ всем объектам ко всем сервисам. Но «локальный сервис-локатор» в вашей терминологии лишён этого недостатка. вы создаёте его перед инъекцией и включаете только то, что нужно. Так (на данный момент) вижу ситуацию я. А какие проблемы усматриваете Вы?
Только тогда поясните мне. Оставим пока вопрос о «знает про инфраструктуру DI». В чём принципиальная разница межу классическим примером DI
и
Пока что я вижу, что второй пример вдвое лаконичее и вдвое читабельнее, и считаю, что если вместо get_listeners() написать Get < ListenersList '>() ничего не изменится. Но верю, что вы можете меня переубедить.
Возвращаемся к вопросу про «знает про инфраструктуру DI»
Как тогда вы относитесь к библиотеке для DI от google, — «fruit», где бы вам пришлось писать
Как видите, для fruit важно, чтобы класс знал об инфраструктуре DI (при этом тоже использует внешнюю инициализацию). Ваше мнение по этому поводу?
З.Ы. Пожалуйста, не используйте термин «локальный сервис-локатор». Он для меня звучит как «локальный синглтон».
З.З.Ы. Опять же, напоминаю, внешнюю инициализацию можно сделать и на TypedSet. Просто интересно понять, зачем.
Но многие стандартные библиотеки для DI используют внешнюю инициализацию. Инжектор аггрегирует, инкапсулирует или просто инициализирует произвольный класс. По вашему: есть ли в этом преимущество перед первым подходом?
моя точка зрения — не пуп земли
минимальн. — это результат ограничения на длину заголовка. двух символов не хватило — либо минимальн. либо це вместо цену
Не наблюдал такого. clang и с O0, и с O3 и gcc на O3 просто херачит прямо runtime хэш имени класса, который задан в typeid info типа. Если тип известен на этапе компиляции — то автоматом загружает нужную typeid таблицу. Если динамическое определение — то из виртуальной таблицы ссылку на typeid таблицу берёт. А вот насчёт MS VC Не скажу — там всё упирается в функцию __std_type_info_hash, которую godbolt не показывает. но судя по расширению .so вы не о мелкософовском компиляторе…
Но компилятор можно и надурить. Если в указателе на невиртуальный предок записан адрес потомка. При указателе на невиртуальный класс компилер не будет разбираться и будет загружать typeid того типа, на который указывает формально указатель gcc.godbolt.org/z/TeNJsE. Тоже, полагаю, будет из shared_ptr — механизм тот же.
А вообще, отсутствие гарантий и UB бывают чисто теоретическими. Пример — атомарность операций с выровненным int. Стандарт не гарантирует — а Intel гарантирует. И arm, кажется, тоже.
Или выход указателей за пределы выделенного куска памяти. По стандарту — это UB, но очень много людей на это полагается и строит, например, проверку выхода за пределы массива.
Если вы читаете асм и с++ — вот вам пример на тему gcc.godbolt.org/z/esZgmf