Комментарии 41
del
Я сейчас правильно понял, что только что был изобретен очередной DI-фреймфорк?
Почему-то все писатели таких «советов» игнорируют, что есть минимум 2 типа интерфейсов — синхронный и асинхронный. И работа с ними очень сильно отличается
… а еще chatty/chunky. Нет, нельзя, нельзя трактовать локальное и удаленное одинаково.
Я просто почему прицепился, уже не в первый раз вижу, когда говорится «просто возьмем и заменим». Вот бы и показали тогда, как это просто. А то попахивает банальным маркетингом идеи, как в разных книжках типа «а давайте накрутим архитектуру ради архитектуры». Не надо так
Вы только что реализовали паттерн Service Provider, ещё один шаг и сделаете что-то вроде Dependency Injection.
скорость доступа не 2*длина_строки*log(число_сервисов)
Бьюсь об заклад, коллеги на Qt использовали QHash.
Если вы читаете асм и с++ — вот вам пример на тему gcc.godbolt.org/z/esZgmf
То есть вы ещё и лжёте
Но я не спец по яве
А на основании чего вы тогда делаете утверждение об "универсальном методе построения архитектуры приложения на [...] Java за минимальн. цену"
PS. Вообще, конечно, вот это "минимальн." — это, как мне кажется, хорошая демонстрация оптимизации.
моя точка зрения — не пуп земли
минимальн. — это результат ограничения на длину заголовка. двух символов не хватило — либо минимальн. либо це вместо цену
Занятно, как обоих вопросов можно было бы избежать, убрав "и Java" из заголовка...
Я предлагаю очень небольшой инструмент параметризации, который позволяет реализовать эти шаблоны с минимумом писанины. В том числе и Dependency Injection и Service Locator Фаулера
Нет, я все-таки спрошу. А как с помощью вашего инструмента реализовать dependency injection?
В первую очередь — про Constructor, конечно же, это самый естественный вариант. Interface мне кажется необоснованно многословным, а Setter слишком часто требует лишних зависимостей (и оба они мешают инвариантам).
Но многие стандартные библиотеки для DI используют внешнюю инициализацию. Инжектор аггрегирует, инкапсулирует или просто инициализирует произвольный класс. По вашему: есть ли в этом преимущество перед первым подходом?
Для меня естественным решением является передать TypedSet или StrategiesSet в конструктор.
… а в конструкторе получить из него нужную имплементацию через Get<T>()
? Так это не DI, это опять service locator, просто локальный, а не глобальный. Со всеми его, сервис локатора, недостатками.
По вашему: есть ли в этом преимущество перед первым подходом?
Конечно, есть. В этом случае функциональные классы ничего не знают про инфраструктуру DI, и полностью от нее независимы (в идеальном случае, конечно, иногда бывают всякие тонкости).
Только тогда поясните мне. Оставим пока вопрос о «знает про инфраструктуру 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», где бы вам пришлось писать
class Server {
public:
INJECTION( 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 ) {
}
(...)
};
Как видите, для fruit важно, чтобы класс знал об инфраструктуре DI (при этом тоже использует внешнюю инициализацию). Ваше мнение по этому поводу?
З.Ы. Пожалуйста, не используйте термин «локальный сервис-локатор». Он для меня звучит как «локальный синглтон».
З.З.Ы. Опять же, напоминаю, внешнюю инициализацию можно сделать и на TypedSet. Просто интересно понять, зачем.
В чём принципиальная разница межу классическим примером DI
В том, что второе — не DI, а Service Locator.
Как тогда вы относитесь к библиотеке для DI от google, — «fruit»
Плохо.
.
Но не в терминологии дело. Вот у нас поменялось написание геттера. В чём негатив? Сервис-локатор, как я понимаю, плох тем, что он нарушает инкапсуляцию и модульность, давая доступ всем объектам ко всем сервисам. Но «локальный сервис-локатор» в вашей терминологии лишён этого недостатка. вы создаёте его перед инъекцией и включаете только то, что нужно. Так (на данный момент) вижу ситуацию я. А какие проблемы усматриваете Вы?
В моём представлении Service Locator — это штука, единая для всех классов.
"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. "
Это ровно то, что у вас: есть некий объект (TypedSet
), которому можно сказать Get<Dependency>
, и получить эту зависимость. Или не получить, если она не зарегистрирована.
Если Вы создаёте объект для каждого класса индивидуально, со своим набором «сервисов» — то это уже параметризация.
Класс-потребитель не знает, как был создан объект, который он получил, и исходит из того, что там есть все сервисы, которые ему могут понадобиться.
Но не в терминологии дело. Вот у нас поменялось написание геттера. В чём негатив?
Дело не в геттере (еще и потому, что "в норме" геттера в DI нет), дело в том, что потребитель не объявляет и не получает свои зависимости явно.
Сервис-локатор, как я понимаю, плох тем, что он нарушает инкапсуляцию и модульность, давая доступ всем объектам ко всем сервисам.
Нет. Сервис-локатор плох тем, что он скрывает то, какие зависимости потребляет реализация.
«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 проверку на неиспользованные… в вашей терминологии — «сервисы». Будет однозначный контроль — не дашь нужный сервис, сработает ассерт. Дашь лишний — сработает ассерт. При этом будет печатать файл, строку и функцию, в которой сделан кривой инжект. и список избыточных зависимостей (или указание на ту зависимость, которой не хватило). Таким образом проблема сокрытия зависимостей, потребляемых реализацией, перестанет быть проблемой. Что скажете?
я считаю важным ОБЯЗАТЕЛЬНО ограничивать доступ объекта теми сервисами, которые ему нужны.
И dependency injection справляется с этим лучше, чем ваше решение.
Имхо, Вы используете неверное слово — не “ему могут понадобится», а «он использует». Засылать зависимости впрок — странно
Но вы же не знаете, какие зависимости используются объектом, он не под вашим контролем. Поэтому придется передать все.
Смысл моей статьи — неопределённая параметризация, то есть в Вашей терминологии — параметризация без без явного объявления зависимостей.
Значит, это не DI.
Я в статье утверждаю, что это делает код проще, читабельнее, его написание — быстрее, и за счёт простоты и повышения читабельности упрощает отладку.
Проще — да. Быстрее писать — да. Читабельнее — нет, проще отлаживать — тоже нет. Если сравнивать с чистым constructor DI, конечно же.
В точке инжекта всегда будут видны зависимости.
… каким образом? Вот я хочу потребить ваш Server
, как мне узнать, что передать ему в конструктор?
Потому что будут с изменением кода передаваться лишние зависимости, и в виду разных мест это будет сложно отследить.
В DI нельзя передать зависимости, которые объект не запрашивает.
Будет однозначный контроль — не дашь нужный сервис, сработает ассерт. Дашь лишний — сработает ассерт.
Все то, что в constructor injection делается компилятором, да.
Таким образом проблема сокрытия зависимостей, потребляемых реализацией, перестанет быть проблемой. Что скажете?
Скажу, что это все равно service locator.
что передать ему в конструктор?
посмотреть определение конструктора
Скажу, что это все равно service locator.
Не надо давать имена, скажите чем плохо, чтобы я мог лучше продумать код. Запрос на DI поступил именно и только от Вас, поэтому я пытаюсь понять, чего Вы хотите. Если Ваши желания будут мне интересны — я их постараюсь реализовать.
Значит, это не DI.
как минимум, спецы из гугл, писавшие fruit, не разделяют Ваше мнение
инжектор сам разбирался, что подать на выход конструктора
Что значит "сам разбирался"? Что написано в конструкторе, то он и должен передать
посмотреть определение конструктора
И чем поможет это определение?
Server ( shared_ptr<TypedSet> dynamic_parametrization )
как минимум, спецы из гугл, писавшие fruit, не разделяют Ваше мнение
Судя по коду, который вы привели выше, при использовании fruit зависимости явно объявляются в конструкторе. Поэтому во fruit нет " неопределённой параметризации"
Правильно ли я понимаю, что вы хотите чтобы инжектор сам разбирался, что подать на выход конструктора?
Я, в первую очередь, хочу, чтобы конструктор явно получал нужные классу зависимости. А во вторую — чтобы это не надо было писать руками.
посмотреть определение конструктора
Ну вот смотрим:
Server ( shared_ptr<TypedSet> dynamic_parametrization )
Из этого мы не знаем, какие зависимости нужны Server
(или, что то же самое, как сконфигурить TypedSet
).
Не надо давать имена, скажите чем плохо, чтобы я мог лучше продумать код
Ну так имена нужны ровно для того, чтобы можно было сказать: плохо тем, чем service locator хуже dependency injection — неявностью потребляемых зависимостей.
Запрос на DI поступил именно и только от Вас
Это не так. После того, как вы сказали, что с помощью вашего инструмента можно сделать DI, я поинтересовался как.
как минимум, спецы из гугл, писавшие fruit, не разделяют Ваше мнение
Мне, если честно, все равно.
А вообще, отсутствие гарантий и UB бывают чисто теоретическими. Пример — атомарность операций с выровненным int. Стандарт не гарантирует — а Intel гарантирует. И arm, кажется, тоже.
Или выход указателей за пределы выделенного куска памяти. По стандарту — это UB, но очень много людей на это полагается и строит, например, проверку выхода за пределы массива.
Не наблюдал такого. 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 — механизм тот же.
А мне нравится стратегии передавать в виде шаблонных аргументов. Производительность приятно удивляет.
Неопределённая параметризация как универсальный метод построения архитектуры приложения на C++ и Java за минимальн. цену