Re[2]: Интерфейсы и реализация
От: Barbar1an Украина  
Дата: 05.09.20 15:49
Оценка:
Здравствуйте, Sharov, Вы писали:

S>.net core, С#. Стал с недавнего времени использовать DI, а там без интерфейсов практически никуда.


причем максимум у них 2 реализации а если не заморчиваться с тестами то ваще 1, сам пишу, но это хрень и дичь
Я изъездил эту страну вдоль и поперек, общался с умнейшими людьми и я могу вам ручаться в том, что обработка данных является лишь причудой, мода на которую продержится не более года. (с) Эксперт, авторитет и профессионал из 1957 г.
Отредактировано 05.09.2020 15:50 Barbar1an . Предыдущая версия .
Re: Интерфейсы и реализация
От: Shtole  
Дата: 08.09.20 17:00
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Чем руководствуетесь, принимая решение выделять или не выделять интерфейс из класса, т.е. нужно ли разделить интерфейс и реализацию?


Б>Когда имеется несколько реализация — понятно, интерфейс обычно выделяют.

Б>Но если реализация только одна, для каких классов выделяете интерфейсы, а для каких нет?

В тех случаях, когда может/должна появиться независимая реализация. Наличие интерфейса при одной-единственной реализации это, собственно, способ сказать читателю кода: «Появится другая, возможно, я знаю, какая, а возможно — имею об этом лишь смутное представление».

Важными считаю 2 момента:

1. Иногда реализация так и остаётся единственной. Например, библиотека умирает вместе с проектом, платформой etc. Но это НЕ означает автоматически overengineering, ведь фиксировать свои мысли об архитектуре надо по мере их появления, начиная с самой ранней версии. Overengineering'ом это может быть в каждом конкретном случае. Или не быть. Поэтому когда бараны кругом начинают блеять своё «YAGNI», надо требовать доказательств аргументов применительно к данному классу и данному интерфейсу. Общий случай не катит. Хотя в частном они могут оказаться правы.

  Скрытый текст
Как вы понимаете, такой подход обесценивает YAGNI в принципе.


2. В каждом конкретном случае думать приходится творчески, к механическим правилам это свести невозможно. (Невозможно заложить правила проверки в static code analyser). Такой ответ, наверно, кого-то разочарует, но кого-то, наоборот, успокоит: у всех так, посоны.

Б>P.S. Возможно, ответ сильно зависит от используемого языка. По возможности, укажите используемый язык.


Зависит мало, на самом деле. 20 лет назад в плюсах дефайнили interface на class/struct и вперёд. Не для компилятора же мы это писали.
Do you want to develop an app?
Отредактировано 08.09.2020 17:04 Shtole . Предыдущая версия .
Re: Интерфейсы и реализация
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.09.20 18:55
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Чем руководствуетесь, принимая решение выделять или не выделять интерфейс из класса, т.е. нужно ли разделить интерфейс и реализацию?


Б>Когда имеется несколько реализация — понятно, интерфейс обычно выделяют.

Б>Но если реализация только одна, для каких классов выделяете интерфейсы, а для каких нет?

Есть такое соображение, как минимизация кол-ва перестраиваемых модулей/сборок/пакетов. Руководствуясь им (в том числе им), принимаю решение о том, что нужно разделить интерфейс и реализацию даже в случае единой реализации. Интерфейсы кидаются в стабильный пакет, а реализация живет в нестабильном, который подключается динамически. Или даже статически, но уже на этапе внедрения зависимостей.

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

Б>P.S. Возможно, ответ сильно зависит от используемого языка. По возможности, укажите используемый язык.
Re[2]: Интерфейсы и реализация
От: diez_p  
Дата: 21.10.20 14:32
Оценка:
Здравствуйте, СвободуАнжелеДевис, Вы писали:


Б>>Когда имеется несколько реализация — понятно, интерфейс обычно выделяют.

Б>>Но если реализация только одна, для каких классов выделяете интерфейсы, а для каких нет?

САД>Выделяется при любом взаимодействии двух и более классов, ибо тесты.

За такое гореть в аду, фреймворки давно уже умеют мокать классы, если даже таких нет, можно дописать велосипедов самостоятельно, но при этом контракттипа не раздувая.

Б>>Но если реализация только одна, для каких классов выделяете интерфейсы, а для каких нет?

Когда обращение должно быть по интерфейсу, вместа типа. Например класс внутренняя реализация модуля (internal) создает этот тип какой-то провайдер и необходимо оставить реализацию типа внутри билиотеки, либо может внутри какого-то namespace (физически нельзя запретить, но соглашением можно)
Re[2]: Интерфейсы и реализация
От: diez_p  
Дата: 21.10.20 14:33
Оценка:
S>.net core, С#. Стал с недавнего времени использовать DI, а там без интерфейсов практически никуда.
А что вам мешает подсоввать тип напрямую?
Re[3]: Интерфейсы и реализация
От: Sharov Россия  
Дата: 21.10.20 19:49
Оценка:
Здравствуйте, diez_p, Вы писали:

S>>.net core, С#. Стал с недавнего времени использовать DI, а там без интерфейсов практически никуда.

_>А что вам мешает подсоввать тип напрямую?

А зачем тогда DI? А если я конфигурирую реализации в конфиге, и тип узнаю только в runtime?
Кодом людям нужно помогать!
Re[4]: Интерфейсы и реализация
От: diez_p  
Дата: 24.10.20 03:45
Оценка: +1
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, diez_p, Вы писали:


S>>>.net core, С#. Стал с недавнего времени использовать DI, а там без интерфейсов практически никуда.

_>>А что вам мешает подсоввать тип напрямую?

S>А зачем тогда DI? А если я конфигурирую реализации в конфиге, и тип узнаю только в runtime?

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

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

Условно есть три сервиса
S1, S2, S3 — конкретные типы.

Есть клиенты этих сервисов
C12(S1, S2), C23(S2, S3), C123(S1, S2, S3)
регистрируется все это
Di.Register<S1>
Di.Register<S2>
Di.Register<S3>
Di.Register<C12>
Di.Register<C23>
Di.Register<C123>

при вызове DI.Resolve<C123> контейнер создаст S1, S2, S3, а при вызове DI.Resolve<C12>, только S1, S2
На кой черт при таком дизайне раздувать контракт интерфейсами — не вижу смысла, т.е. сам по себе DI выделения интерфейса не требует.
Re[5]: Интерфейсы и реализация
От: scf  
Дата: 24.10.20 07:01
Оценка:
Здравствуйте, diez_p, Вы писали:

_>На кой черт при таком дизайне раздувать контракт интерфейсами — не вижу смысла, т.е. сам по себе DI выделения интерфейса не требует.


Две причины.

Интерфейс служит документацией к классу, не загромождая её реализацией, интерфейс позволяет подсунуть другую реализацию в юнит-тестах.
Re[6]: Интерфейсы и реализация
От: · Великобритания  
Дата: 24.10.20 07:45
Оценка: +1
Здравствуйте, scf, Вы писали:

_>>На кой черт при таком дизайне раздувать контракт интерфейсами — не вижу смысла, т.е. сам по себе DI выделения интерфейса не требует.

scf>Две причины.
Обсудили же это уже. Плохие это причины.

scf>Интерфейс служит документацией к классу, не загромождая её реализацией,

Такая документация элементарно извлекается автоматически. Писать и поддерживать её вручную — так себе развлечение.

scf>интерфейс позволяет подсунуть другую реализацию в юнит-тестах.

Это проблема конкретных мок-фреймворков. Вменяемые фреймворки умеют мокать публичные методы класса.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[16]: Интерфейсы и реализация
От: Mystic Artifact  
Дата: 24.10.20 08:44
Оценка: +1
Здравствуйте, Sharov, Вы писали:

S>Подождите, мы DI рассматриваем в контексте IoC контейнеров, где мы в явном виде new никогда не вызываем, а используем,

S>например, абстрактную фабрику. Ну так вот IoC у меня созвучен с dip, в том плане, что это dip для контроля управления.
S>А dip говорит, что завязываться на детали (реализации) плохо, лучше завязываться на абстракции. Т.е. мы куда-то встраиваемся
S>(во фреймворк) через интерфейсы и абстрактные классы.

DIP во многом зависит и от ЯП, и тут есть некоторые нюансы, и Мартин об этом писал, кстати, что в C++ хидер-файл включает все детали класса, включая все его приватные методы или поля, и даже инклюдит соответственно то, что ему нужно для приватных членов, и "настоящее" разделение там проще сделать через наследование от чистого абстрактного класса (считай это интерфейс в Java/C#).

В Java/C# — напротив — мы имеем автоматическое отделение интерфейса от реализации, в добавок с символическим разрешением зависимостей в рантайме. Ты можешь положить в сборку MyClass1 и будешь видеть исключительно его публичный контракт (публичный интерфейс), без каких либо дополнительных телодвижений, а его изменение лайаута — тебя вообще не волнует, рантайм позабоится об этом сам. И таким образом MyClass1 для всех потребителей — и есть уже эта самая абстракция, интерфейс.

Введение же интерфейсов или абстрактных классов в терминах C#/Java — должны уже иметь на это основания: точки расширения в фреймворках, множество реализаций и т.п.
Re[16]: Интерфейсы и реализация
От: Mystic Artifact  
Дата: 24.10.20 13:34
Оценка: 80 (1)
Здравствуйте, Sharov, Вы писали:

S>Подождите, мы DI рассматриваем в контексте IoC контейнеров, где мы в явном виде new никогда не вызываем, а используем,

S>например, абстрактную фабрику. Ну так вот IoC у меня созвучен с dip, в том плане, что это dip для контроля управления.
S>А dip говорит, что завязываться на детали (реализации) плохо, лучше завязываться на абстракции. Т.е. мы куда-то встраиваемся
S>(во фреймворк) через интерфейсы и абстрактные классы.

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

Раз, для тебя близок ASP.NET Core, — то далеко ходить и не будем. Можно взять его ILogger, как пример. К нему, как к инфраструктурному компоненту должны предъявляться повышенные требования, а на деле — он сам себя закрывает за ущербным интерфейсом из 3 методов, при чём поведение третьего метода — не гарантированно (scoped logging). Реально используемые методы (типа LogDebug) — выполнены вообще в виде экстеншнов, хотя здесь практически нет места свободному расширению, как в LINQ, которые в итоге форсируют использовать using namespace, даже если у тебя доступ к логгеру уже есть.

Ну и вишенка на торте, что практически у любого логгера первое, что он делает, это условие на подобии такого:

  if (logEventLevel < _currentMinimumLevel) return;
  // slow path


Так вот, в здоровых фреймворках, это условие всегда инлайнится, позволяя опустить сам вызов полностью. В добавок даже если вызов и произойдет — в вызове на подобии LogDebug, даже нет смысла в коде передавать уровень как параметр — уменьшая этим самым непосредственно размер исполнимого кода, при этом сохранив цепочку вызовов минимальной (у MS.Logging этого не происходит, спасибо экстеншн методам).

Ровно как и здоровые фреймворки, никогда не будут делать так:
public static void LogTrace(this ILogger logger, Exception exception, string message, params object[] args)


Предпочитая следующие вызовы:
public void LogTrace<T1>(Exception exception, string message, T1 arg1);
public void LogTrace<T1, T2>(Exception exception, string message, T1 arg1, T2 arg2);
public void LogTrace<T1, T2, T3>(Exception exception, string message, T1 arg1, T2 arg2, T3 arg3);


Да, да, всё ради того же: избавиться от холостых выделений памяти, да и generic инвокация окажется короче. Но, опять же, это невозможно, т.к. мы ограничены ущербным интерфейсом.

Это не особо важно для уровней Info и выше, т.к. они практически всегда логгируются, и механика материализации съест немало, но уровни Trace и Debug — в нормальном режиме практически всегда подавлены и достаточно чувствительны к этому. (И не надо петь про дешевость вызовов — любой, даже безусловный переход (jmp) съедает слот в предсказателе переходов.)

Ну и с практической точки зрения, для пользователя, абсолютно фиолетово, что использовать ILogger, Logger, тем более в контейнере можно ещё зарегистрировать и NLog.Logger и Serilog и всё это будет вместе жить через адаптеры, при чём бэкэндом будет любой из них. В конце концов Microsoft.Extensions.Logging — по отношению к твоему коду — просто ещё одна бибилиотека со своим интерфейсом, и фактически — со своим поведением и с единственной реализацией.

Там есть и другие нюансы у них, но по сути, я вел не к тому, какое плохое или хорошее у них логгирование (оно вполне достаточное и фреймворк достаточно универсальный... что бы его не использовать), а просто пример, того, как легко запереть себя в "абстракциях" отрезав любые эффективные решения как класс. В данном случае — абстракция (в виде интерфейса) — вообще не нужна.
Отредактировано 24.10.2020 13:44 Mystic Artifact . Предыдущая версия . Еще …
Отредактировано 24.10.2020 13:42 Mystic Artifact . Предыдущая версия .
Отредактировано 24.10.2020 13:35 Mystic Artifact . Предыдущая версия .
Re[17]: Интерфейсы и реализация
От: · Великобритания  
Дата: 24.10.20 18:52
Оценка: +1
Здравствуйте, Mystic Artifact, Вы писали:

S>>Подождите, мы DI рассматриваем в контексте IoC контейнеров, где мы в явном виде new никогда не вызываем, а используем,

S>>например, абстрактную фабрику. Ну так вот IoC у меня созвучен с dip, в том плане, что это dip для контроля управления.
S>>А dip говорит, что завязываться на детали (реализации) плохо, лучше завязываться на абстракции. Т.е. мы куда-то встраиваемся
S>>(во фреймворк) через интерфейсы и абстрактные классы.
MA> DIP во многом зависит и от ЯП, и тут есть некоторые нюансы, и Мартин об этом писал, кстати, что в C++ хидер-файл включает все детали класса, включая все его приватные методы или поля, и даже инклюдит соответственно то, что ему нужно для приватных членов, и "настоящее" разделение там проще сделать через наследование от чистого абстрактного класса (считай это интерфейс в Java/C#).
Согласен. Стоит отметить, что это тоже довольно условно относится к интерфейсам. В плюсах разделять можно например через CRTP или даже pImpl, без всякого наследования и интерфейсов. И уж тем более это всё ортогонально к DI.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: Интерфейсы и реализация
От: Sharov Россия  
Дата: 25.10.20 12:51
Оценка:
Здравствуйте, diez_p, Вы писали:

S>>А зачем тогда DI? А если я конфигурирую реализации в конфиге, и тип узнаю только в runtime?

_>DI решает какие объекты когда создавать, так же он заботится о том, чтобы при вызове конструктора типа, туда были переданы правильные требуемые инстансы.
_>Все равно непонятно почему даже в конфиге вам надо указывать именно интерфейсы, а не конкретные реализации.

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

_>Конфиг для контейнера нужен, если кто-то меняет реализацию без пересборки проекта, если этого не происходит выкинуть конфиг и зашить все в код.


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

_>Условно есть три сервиса

_>S1, S2, S3 — конкретные типы.

_>Есть клиенты этих сервисов

_>C12(S1, S2), C23(S2, S3), C123(S1, S2, S3)
_>регистрируется все это
_>Di.Register<S1>
_>Di.Register<S2>
_>Di.Register<S3>
_>Di.Register<C12>
_>Di.Register<C23>
_>Di.Register<C123>

_>при вызове DI.Resolve<C123> контейнер создаст S1, S2, S3, а при вызове DI.Resolve<C12>, только S1, S2

_>На кой черт при таком дизайне раздувать контракт интерфейсами — не вижу смысла, т.е. сам по себе DI выделения интерфейса не требует.

Если завязываться на DI, то уж лучше использовать интерфейсы и пользоваться всеми плюшками DI. В противном случае и DI не нужен.
Кодом людям нужно помогать!
Re[6]: Интерфейсы и реализация
От: diez_p  
Дата: 27.10.20 14:57
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, diez_p, Вы писали:


S>>>А зачем тогда DI? А если я конфигурирую реализации в конфиге, и тип узнаю только в runtime?

_>>DI решает какие объекты когда создавать, так же он заботится о том, чтобы при вызове конструктора типа, туда были переданы правильные требуемые инстансы.
_>>Все равно непонятно почему даже в конфиге вам надо указывать именно интерфейсы, а не конкретные реализации.

S>Вероятно я был не так понят, но в конфиге мы указываем реализацию, а не интерфейс. Интерфейс в коде, в конфиге только реализация.


_>>Конфиг для контейнера нужен, если кто-то меняет реализацию без пересборки проекта, если этого не происходит выкинуть конфиг и зашить все в код.


S>Так то оно так, но для тестируемости и гибкости лучше все же оставить интерфейсы. Если бизнес требования поменяются?

Вот как поменяются бизнес требования, так и допишите, при этом интерфейс возможно и поменяется.
Это называется излишнее проектирование наперед. Об это много пишут в книжках.
Ну и потом такой подход это сродни инвестированию. Добавить интерфейсы в Java/C# и заменить вызовы делется быстро, нет смысла это делать наперед. Если же делать что-то наперед тяжело изменяемое, то для этого надо иметь весомые обоснования.

Про интерфейсы только для тестирования выше писали и я тоже писал http://rsdn.org/forum/dotnet/7826666.1
Автор: diez_p
Дата: 10.09.20

интерфейс ради тестирования — раздувание контракта. Фреймворки умеют мокать методы классов, если не умеют можно написать велосипед.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.