LINUX.ORG.RU

Dependency Injection что это за беспредел?

 


0

4

В процессе осваивания Java дошел до такой темы как DI и что мне там предлагают. Вместо того что бы писать как деды завещали

class Car {
  private Engine engine;
  public Car() {
    engine = new Engine()
  }
}

Car car = new Car();

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

class Car {
  private Engine engine;
  public Car(Engine engine) {
    this.engine = engine;
  } 
}

Engine engine = new Engine();
Car car = new Car(engine);
И так дескать получается лучше и моками удобней обкладывать и рефакторить и еще можно там чего-то делать, а я вижу, что раньше клиент класса знал только про один объект - Car, а теперь ему приходится еще слушать про Engine. А как же инкапсуляция? Зачем ему нужно про этот Engine знать.



Последнее исправление: da17 (всего исправлений: 2)

раньше клиент класса знал только про один объект - Car, а теперь ему приходится еще слушать про Engine. А как же инкапсуляция? Зачем ему нужно про этот Engine знать.

Нет. Клиенту класса car приходит уже сконструированный, ему не нужно знать про Engine.

class CarClient {
    private final Car car;

    CarClient(Car car) {
        this.car = car;
    }

    void useCar() {
        car.use();
    }
}

Про Engine нужно знать тому, кто конструирует весь граф объектов. Это может быть просто метод main, это может быть фреймворк типа Spring.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от Legioner

Получается если это main, то main должен знать про Engine? В первом же случае, что я написал про Engine знает только Car. Или речь идет про то что там фреймворки вроде Spring или Dagger все это разруливают?

da17
() автор топика
Ответ на: комментарий от da17

Получается если это main, то main должен знать про Engine?

Да, тот, кто конструирует граф объектов, должен знать про все их реализации.

В первом же случае, что я написал про Engine знает только Car.

И в этом нет ничего хорошего. Между классами Engine и Car получается тесная связь. Мало того, что Car зависит от публичного интерфейса Engine, так он ещё и привязан к конкретной реализации этого класса. Например ты не сможешь заменить бензиновый двигатель электродвигателем, просто подсунув его в конструкторе. Т.е. такой дизайн ограничивает гибкость твоего приложения. Или, например, подсунуть Car-у какой-то тестовый Engine, чтобы протестировать Car не тратя бензин (конечно аналогии так себе, но, думаю, уловить можно).

Или речь идет про то что там фреймворки вроде Spring или Dagger все это разруливают?

Ну на практике DI обычно используют со фреймворками, которые всё это разруливают. Но ничего не мешает делать это руками, особенно если проект небольшой.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от Legioner

И в этом нет ничего хорошего. Между классами Engine и Car >получается тесная связь. Мало того, что Car зависит от публичного >интерфейса Engine, так он ещё и привязан к конкретной реализации >этого класса. Например ты не сможешь заменить бензиновый >двигатель электродвигателем, просто подсунув его в конструкторе. >Т.е. такой дизайн ограничивает гибкость твоего приложения. Или, >например, подсунуть Car-у какой-то тестовый Engine, чтобы >протестировать Car не тратя бензин (конечно аналогии так себе, >но, думаю, уловить можно).

Ок, спасибо, согласен с этим на все 100%, но если я четко знаю, что мне нужно. Получается, что это сам подход такой, когда не знаешь куда тебя дальше забросит проект, но если все высчитано, получается ведь избыточность некая.

А что значит «проект небольшой»? У меня пока где-то 6 000 строк кода без учета тестов

da17
() автор топика
Последнее исправление: da17 (всего исправлений: 1)
Ответ на: комментарий от da17

Ок, спасибо, согласен с этим на все 100%, но если я четко знаю, что мне нужно. Получается, что это сам подход такой, когда не знаешь куда тебя дальше забросит проект, но если все высчитано, получается ведь избыточность некая.

Это усложнение архитектуры, которое даёт большую гибкость. Иногда это надо, иногда это не надо.

А что значит «проект небольшой»? У меня пока где-то 6 000 строк кода без учета тестов

Когда задолбаешься вручную прописывать все эти зависимости, значит проект стал достаточно большим. Думаю, это примерно при сотне сервисов начнётся.

Тут ещё надо понимать, что это всё DI применяется не для всех классов, а для классов с поведением. Всякие XxxService, XxxManager и тд. Если у тебя просто класс-структура с кучкой данных без особой логики, естественно тут оно ни к чему.

Legioner ★★★★★
()

А кто рекомендует?
Правильно писать как раньше и, при нужде, делать второй конструктор, принимающий engine (как во втором случае).
Тут железобетонная логика - ты можешь и просто создать объект, который сам все сделает, если тебя устраивает дефолтный engine, так и создать engine заранее, и передать его конструктору.
Не слушай современных смузихлебов, деды делали по уму, деды делали на совесть

rukez ★★★★
()
Ответ на: комментарий от rukez

Как писали деды:

        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema;
        try (StringReader schemaStringReader = new StringReader(schemaString)) {
            schema = schemaFactory.newSchema(new StreamSource(schemaStringReader));
        }
        Validator validator = schema.newValidator();

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document;
        try (StringReader documentStringReader = new StringReader(documentString)) {
            document = documentBuilder.parse(new InputSource(documentStringReader));
        }

        validator.validate(new DOMSource(document));
Legioner ★★★★★
()

Ему не нужно знать про engine. Например, в клиент может инжектиться car, либо инстанс car создаётся через фабрику. Всё это нужно ради гибкости. Иначе ты хардкодишь new Engine() внутри Car, не даёшь возможности подсунуть инстанс другого класса, но того же интерфейса; если у конструктора Engine есть парамерты, то ты их тоже хардкодишь. Если подобаня гибкость в каком-то конкретном случае не нужна и ты уверен, что она не будет нужна в будущем, то пиши как хочешь.

fulmar_lor
()
Ответ на: комментарий от Legioner

Как надо писать:

    new ServiceExecutionJoinPoint(
      DistributedQueryAnalyzer.forwardQueryResult(
        NotificationSchemaManager.getAbstractSchemaMapper(
          new PublishSubscribeNotificationSchema()).getSchemaProxy().
            executePublishSubscribeQueryPlan(
              NotificationSchema.ALERT,
              new NotificationSchemaPriority(SchemaPriority.MAX_PRIORITY),
              new PublisherMessage(MessageFactory.getAbstractMessage(
                MessageType.WRITTEN,
                new MessageTransport(MessageTransportType.WOUNDED_SURVIVOR),
                new MessageSessionDestination(
                  DestinationManager.getNullDestinationForQueryPlan()))),
              DistributedWarMachine.getPartyRoleManager().getRegisteredParties(
                PartyRoleManager.PARTY_KING ||
                PartyRoleManager.PARTY_GENERAL ||
                PartyRoleManager.PARTY_AMBASSADOR)).getQueryResult(),
        PriorityMessageDispatcher.getPriorityDispatchInstance())).
      waitForService();
eve
()
Ответ на: комментарий от da17

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

но если все высчитано

Так не бывает.

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

А еще бывает что нужно уметь одновременно в разные реализации.

Например пульсомеры. Там есть разные варианты общения с ними, и ты хочешь поддерживать максимально много. Пишешь один раз интерфейс, и потом реализуешь его в разных вариантах: ble, ant+, etc.

В зависимости от того какой пульсометр у юзверя есть, тот инстанс и подсовываешь. А коду который им пользуется, вообще до лампочки что там за протокол и что за реализация.

redixin ★★★★
()
Ответ на: комментарий от Legioner

Ок, спасибо, согласен с этим на все 100%, но если я четко знаю, что мне нужно. Получается, что это сам подход такой, когда не знаешь куда тебя дальше забросит проект, но если все высчитано, получается ведь избыточность некая.

Это усложнение архитектуры, которое даёт большую гибкость. Иногда это надо, иногда это не надо.

Называется YAGNI.

utf8nowhere ★★★
()

Если кратко, то DI - это зло, которое надо использовать только в случае крайней необходимости. И в Вашем примере оно ещё не так страшно. Обычно же используют всякие хитрые фреймворки (в C# мне пришлось столкнуться с Autofac, например), в которых зависимости между классами строятся специальными билдерами через xml-конфиг и разобрать что с чем связано в проекте становится нетривиальной задачей.

Моду на DI ввёл Мартин Фаулер. Это было следствием моды на юнит-тестирование, которую ввёл друг Фаулера - Кент Бек. Проблема тут следующая: сама разработка через тестирование (TDD) вещь интересная, книгу по этой теме рекомендую к прочтению всем. Но вот переделывание старых проектов под юнит-тесты - это зло и бессмысленное насилие над программистами. Ибо юнит-тесты для кода, а не код для юнит-тестов. Если какой-то код удобнее писать без юнит-теста, то в помойку надо отправлять этот юнит-тест, а не код.

Есть ещё последователь Бека - Майкл К. Физерс (книга «Эффективная работа с унаследованным кодом»), который ввёл понятие «характеристический тест», то есть юнит-тест, который натягивается на существующий код для рефакторинга. Но это уже совсем странная вещь, ибо чтобы натянуть юнит-тест на старый код, надо этому коду сделать рефакторинг.

Kogrom
()
Последнее исправление: Kogrom (всего исправлений: 1)

Engine engine = new Engine();

Ты не понял. Инстанс engine либо «магически» инжектится в конструктор (фреймворком), либо ты получаешь его из контейнера явно, но не по статически заданному имени класса new Engine, а динамически из контейнера, типа diContainer.get("engine").

no-such-file ★★★★★
()
Ответ на: комментарий от Kogrom

Если кратко, то DI - это зло, которое надо использовать только в случае крайней необходимости

«Dependency injection», «Inversion of control» — это словосочетания на тысячу долларов, которые описывают фактический процессы ценой в один доллар. Тот, кто умеет их применять, точно так же не применяет этих слов, как человек, который умеет ходить, не описывает свое перемещение как череду контролируемых падений, которую можно описать моделью прямого и обратного маятника. Как правило, если человек интенсивно полагается на DI, IoC, SOLID, GRASP, то перед тобой примерно такой же калека, как человек с тяжелым ДЦП, который много знает про технику ходьбы.

Такие кретины, к сожалению, составляют большинство разрабов на Java, что неизбежно для любой отрасли с деньгами. Больше всего у меня горит с того, что некоторые люди не понимают, что Java и JavaScript — это разные языки, и начинают городить DI, IoC, по SRP плодить один класс на каждый чих и помещать этот класс в отдельный файл в отдельной папке, подпапке, или подподподподподподподпапке, так что длина пути ко включаемому файлу становится 200+ символов. К счастью, таких разрабов не так много, потому что не все кретины изучают JS по книжкам о Java.

byko3y ★★★★
()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от Legioner

Как писали деды:
XMLConstants
try (StringReader schemaStringReader = new StringReader(schemaString)) {
try (StringReader documentStringReader = new StringReader(documentString)) {

не тренди так деды не могли писать.

vtVitus ★★★★★
()
Ответ на: комментарий от da17

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

Это просто некоторая стандартизация подхода, не более не менее. Ты пишешь проект, потом увольняешься, а на твое место приходит другой. И он начинает не разбираться в твоих велосипедах, а уже имеет некоторое представление, что и как и начинает разбираться в логике приложения, а не в коде. Для себя любимого можно и повелосипедить, а в коммерческой нужно следовать некоторым правилам, чтоб не было обидно за весь написанный тобой код. Но повторюсь это просто стандартизация (в рамках проекта) некоторого подхода в построении каркаса приложения.

vtVitus ★★★★★
()
Последнее исправление: vtVitus (всего исправлений: 2)

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

OxiD ★★★★
()
Ответ на: комментарий от Legioner

Так до сих пор пишут тогда, когда надо иметь возможность из одного билдера строить разные подклассы, используя именно подклассовые конструкторы

Есть альтернатива - функция сопоставления типа класса с объектом класса и вызов конструктора у созданного функцией конструктора

А это не имеет никакого отношения к инъекции на стадии создания - в конструкторе может быть овердофига других телодвижений - если их всех делать через инъекции то класс становится не нужен а стилистика превращается в Паскаль (это не плохо местами, просто зачем тогда городить класс?)

rukez ★★★★
()
Ответ на: комментарий от no-such-file

Просто объясните на пальцах чем это лучше дедовских getter-setter->bean, где все точно так-же можно получать/отправлять, только в любой момент времени и без принудиловке при инициализации. И да, это все то-же было доступно любым надстройкам со времён динозавров и мамонтов

rukez ★★★★
()
Ответ на: комментарий от rukez

Так до сих пор пишут тогда, когда надо иметь возможность из одного билдера строить разные подклассы, используя именно подклассовые конструкторы
Есть альтернатива - функция сопоставления типа класса с объектом класса и вызов конструктора у созданного функцией конструктора

Знаете, когда я читаю этот код, у меня возникает впечатление, что это программно генерированный IR какого-то компилятора/шаблонизатора. «Вы, друзья, как ни садитесь, а в музыканты не годитесь». Даже MS XML, который, казалось бы, возник примерно в ту же эпоху и позже был включен через минимальную прокладку в .NET, умеет в полиморфизм. Например, позволяет грузить документ из строки, из массива байтов, из другого документа, и просто из абстрактного потока:

https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms762722(v...

«This may be an URL (String/BSTR), a Request object (in an ASP page), an IStream, SAFEARRAY of bytes (VT_ARRAY|VT_UI1), a DOMDocument object, or any object that supports IStream, ISequentialStream, or IPersistStream»

В шарпе, соответственно, полиморфизм реализован перегрузкой:

https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.load?view=...

Слава богу, что я не подался в жава-программисты.

byko3y ★★★★
()
Ответ на: комментарий от Legioner
XmlTextReader xtr = new XmlTextReader(fileName);
XmlValidatingReader vreader = new XmlValidatingReader(xtr);
vreader.ValidationType = ValidationType.Auto;
vreader.ValidationEventHandler += 
  new ValidationEventHandler(this.ValidationEventHandle);
vreader.Read();
vreader.MoveToContent();
while (vreader.Read()) {}
xtr.Close();
vreader.Close();

https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmltextreader?view=net...

Аналогично, есть конструкторы XmlTextReader с файлом, потоком, строкой, URL, другим XML документом. Потому приведенный тобой код работы с XML стандартной либой жввы не оправдан никакой гибкостью — это просто говноархитектура, либо рожденная эффективными менеджерами, либо ставшая следствием ущербности ранней реализации самой жавы. Причем, я все-таки склоняюсь к первому, поскольку интерфейсы были еще в первой жаве, хоть обобщения и появились только к пятой. Все-таки COM/ActiveX изначально были построены вокруг интерфейсов, а не объектов, в то время, как жаву создали эффективные менеджеры на волне хайпа ООП,

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Ты не догнал зачем это нужно:

  • есть класс А с полем int i, полем type=1 и конструктором (i =1)
  • есть класс Б extends А с type=2 и конструктором (i = 2)
  • есть класс В extends A c type=3 и конструктором (i = 3)

И есть нужда из, например данных БД, создать нужный класс.
В таком случае ты либо делаешь функцию подбора класса, которая в зависимости от type возвращает А.class или В.class или С.class, либо сразу строишь фабрику/билдер объектов

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

Вариантов много, но по итогам все они сводятся к выбору класса по признаку типа.

rukez ★★★★
()
Ответ на: комментарий от byko3y

Ты просто не понимаешь, зачем так сделано. Причём я тебе уже написал зачем, а ты до сих пор не понимаешь. Суть Java, по крайней мере в те времена была в открытости и расширяемости. Тот же стек библиотек для работы с XML в Java заменяемый. И имеется несколько конкурирующих реализаций этого стека (реализующих идентичный интерфейс). Это не .NET, где все шагали в ряд с Microsoft. И так со многими другими компонентами. Отсюда и фабричные методы. Хорошо это или плохо, я не знаю, мне никогда не нужна была реализация, отличная от Xerces-а, включённого в стандартную JDK, но вполне допускаю, что кому-то это было важно. То же с реализацией DOM, например, XPath, XSLT и тд.

Впрочем могу согласиться, что интерфейс для пользователя могли бы сделать и получше.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от Legioner

Ты просто не понимаешь, зачем так сделано. Причём я тебе уже написал зачем, а ты до сих пор не понимаешь. Суть Java, по крайней мере в те времена была в открытости и расширяемости

Интерфейс парсера представляет собой эдак половину всего парсера. Потому что это в принципе прокладка.

И имеется несколько конкурирующих реализаций этого стека (реализующих идентичный интерфейс). Это не .NET, где все шагали в ряд с Microsoft

.NET и Mono — это две реализации с одним интерфейсом. Примерно как Xerec и GNU JAXP, которые и являются двумя основными реализациями, только под разными лицензиями.

В итоге с этой погоней за расширяемостью, которая никогда не будет нужна, сделали (на самом деле) нерасширяемое решение с плюс-минус похожими реализациями. FuzzBuzz просто доводит эту идею до абсурда, где на каждый чих есть своя Factory — прямо как в JAXP, на самом деле.

byko3y ★★★★
()
Ответ на: комментарий от rukez

Это в любом языке программирования нужно вне зависимости от его полиморфизнутости, когда речь идёт о десериализации

Нет, не в любом языке — так делают только в жаве, C#, и еще иногда в C++, когда автор обдолбался книжек по паттернам ООП. В языках с развитым полимофизмом создают объект, не имеющий никакого класса, который выполняет нужную работу с этими данными.

В таком случае ты либо делаешь функцию подбора класса, которая в зависимости от type возвращает А.class или В.class или С.class, либо сразу строишь фабрику/билдер объектов

Честно говоря, я не вижу в твоем примере вообще никакой необходимости в создании новых классов. Ни для данных, ни, тем более, для их фабрик.

byko3y ★★★★
()
Ответ на: комментарий от Legioner

Насколько часто нужна подмена реализаций для таких подкапотных вещей как XML парсер или реализация JPA? Вот чтобы на практике именно.

В дотнете исторически плюрализма нет - eine framework, eine library (c)

Midael ★★★★★
()
Последнее исправление: Midael (всего исправлений: 2)
Ответ на: комментарий от byko3y

После многих лет в дотнете XML стек в яве это довольно пугающая штука. SAX, StAX, JAXB, XStream, Jackson XML (хотя вроде библиотека для json'а), ГПУ, ГПТУ, НТС, КПСС, ЛТД итд итп (с)

В дотнете же XmlReader и XmlSerializer покрывают 99.9% юзкейсов

Midael ★★★★★
()
Ответ на: комментарий от Midael

В дотнете же XmlReader и XmlSerializer покрывают 99.9% юзкейсов

Вот именно: это расширяемость, которая никогда не будет использована, а когда альтернативное решение будет нужно — под него просто сделают новую архитектуру классов. В итоге получается нерасширяемая расширяемость — типичный итог применения экстримального ООП.

Меня больше всего поражает то, что на этом кто-то пишет работающие приложения.

byko3y ★★★★
()
Ответ на: комментарий от Midael

Насколько часто нужна подмена реализаций для таких подкапотных вещей как XML парсер или реализация JPA? Вот чтобы на практике именно.

Мне никогда не нужна была. Но судя по тому, что живых проектов больше одного - кому-то нужно.

В дотнете исторически плюрализма нет - eine framework, eine library (c)

А в жаве исторически стандартизированы интерфейсы. Тот же Java EE это набор интерфейсов с кучей разных реализаций. И в своё время эта вся экосистема была вполне себе живой и активной. В последние годы как будто бы спринг всё вытеснил.

Legioner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.