Pull to refresh
7
0
Константин @astapov

User

Send message
Правильно ли я понимаю, что вы хотите чтобы инжектор сам разбирался, что подать на выход конструктора?

что передать ему в конструктор?

посмотреть определение конструктора

Скажу, что это все равно 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», где бы вам пришлось писать

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. Просто интересно понять, зачем.
Для меня естественным решением является передать 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, а потом захэширует. поэтому код в тексте статьи я попозже дополню более сложным «боевым», более оптимизированным, но менее понятным и читабельным.

Если вы читаете асм и с++ — вот вам пример на тему gcc.godbolt.org/z/esZgmf
Хорошо, сделаю. Но я не спец по яве, так что просьба ногами не бить. В то же время, буду рад любой конструктивной критике.
Сожалею, что недостаточно чётко составил текст, из-за чего у Вас сложилось ложное представление о моих предложениях. Я не предлагаю паттерны — и не реализую их. Я предлагаю лишь маааленький инструментик — на 20 строк — который позволяет реализовывать некоторые паттерны ( в том числе и фаулеровский Service Locator — я правильно понял, вы его имели в виду под Service Provider? ), но не только. Стратегии тоже можно фигачить им. Важно, что в отличие от фаулеровского решения (которого придерживались мои коллеги на Qt) в предложенном инструменте минимум писанины и скорость доступа не 2*длина_строки*log(число_сервисов), а просто log(число_сервисов). Было бы конечно, круто придумать новый паттерн проектирования, но я изначально замахивался на нечто более скромное.
Причём здесь инструмент? сделайте два интерфейса, если работа с ним так различна. какие-нибудь SynchronousWork и AsynchronousWork. Инструмент позволяет сделать и так, и так, а как Вам надо — Вам виднее.
Точно нет. Я не предлагаю никаких фреймворков. Это как сравнить электросамокат с Бентли. Но по результатам комментов я сделал апдейт в тексте. Но в каком-то смысле вы правы — я предлагаю два класса, с помощью которых можно эффективно и дёшево (имхо) параметризовать другой класс, или реализовать Dependency Injection и Service Locator, или без лишней писанины добавить в класс «оптом» десяток стратегий.
Раз столько комментариев на тему переоткрытия мною велосипедов, значит, я недостаточно чётко сформулировал текст. Я не предлагаю паттернов проектирования вообще. Я предлагаю метод их реализации за нулевую стоимость и с минимумом писанины. Причём сам метод укладывается в 20 строк кода. Если кратко, то я предлагаю метод экономной параметризации класса (в смысле числа строк кода и скорости использования) чем угодно. И если вам интересно, я не миллениал и старше Вас.
Чем-то напоминает AlgoLib
Чем-то напоминает Algo
1

Information

Rating
Does not participate
Location
Россия
Registered
Activity