Каждый интерфейс явно реализован? (участвует IoC)

Я очень хорошо осведомлен о том факте, что такой вопрос, вероятно, уже был опубликован. Тем не менее, с участием IoC в этом случае и большим количеством кода, который я видел, коллега в компании, в которой я новичок, поднял этот вопрос.

Сценарий:

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

((IInterface)concreteClass).SomeMethod()

Предыстория:

Этот коллега объяснил мне после того, как спросил, что это за явная реализация всех интерфейсов, что они недавно представили StructureMap, и многие люди до сих пор просто используют конкретные типы. Так что по сути это средство «обучения» людей в компании.

Мои центы по этому поводу:

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

Я также слышал, что это может действительно испортить наследование, но не знаю примера. Я также видел, как Jon Skeet как бы отговаривал его от использования упомянутым выше способом, а скорее так, как он был предназначен для использования, например, с IEnumerable‹> и другими конфликтами имен.

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

Спасибо!

Редактировать: я очень хорошо знаю, что это не вопрос правильного или неправильного, и нет реального ответа. Это скорее вопрос, чтобы научиться знать недостатки каждого подхода. Почему я должен использовать в одном сценарии один подход над другим?


person Alex Endris    schedule 08.09.2014    source источник


Ответы (2)


Есть два основных потенциальных преимущества явных реализаций интерфейса:

  1. Один класс может реализовать два интерфейса, которые имеют конфликтующие сигнатуры методов и несколько разные реализации для каждого из них. Это случается редко, и обычно это не проблема сделать по требованию, а не по умолчанию.
  2. Вы можете «скрыть» определенные методы, которые вы не ожидаете, что люди будут использовать, кроме как в определенных контекстах. Принцип разделения интерфейса призывает нас не предоставлять потребителям методы, которые им вряд ли понадобятся.

Одним из хороших примеров использования № 2 является то, как контексты Entity Framework избегают прямого раскрытия своего базового ObjectContext (поскольку большинству людей не следует использовать его в своем коде), но все же делают это свойство доступным, если вы приводите контекст к IObjectContextAdapter .

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

person StriplingWarrior    schedule 08.09.2014
comment
до 1. Да, именно поэтому вы используете его для реализации, например, IEnumerable и IEnumerable‹›. до 2. Интересно, правильно ли вы поняли провайдера. Интернет-провайдер строго об интерфейсах. Это означает, что вы можете разделить один интерфейс на два интерфейса, потому что потребитель не использует большую часть второго интерфейса, поэтому ему не нужно знать об этом. Явная реализация интерфейса не требует провайдера. Кроме того, нет, ISP не применяется там таким образом. То, что нужно отделить, отделяется, но не через явную реализацию интерфейса. Там другое дело. - person Alex Endris; 09.09.2014
comment
@Qudeid: я думаю, что интернет-провайдер относится к интерфейсам как к концепции, а не как к языковой конструкции. В этом смысле каждый конкретный тип предоставляет интерфейсы с различной видимостью на основе модификаторов конфиденциальности, и каждый interface, который он реализует, также предоставляет другой интерфейс с видимостью на основе своего модификатора конфиденциальности. interface может определять только подмножество методов, реализованных конкретным классом, и (используя явную реализацию интерфейса) класс может предоставлять только подмножество своей общей доступной функциональности в виде общедоступных методов. Ты видишь, откуда я? - person StriplingWarrior; 09.09.2014
comment
Естественно, класс имеет интерфейс своих открытых членов. Вопрос здесь всегда в том, чего я хочу достичь. Например, предложение кода от InBetween. Хотя это на 100% правильный код и работает, это не означает, что это хороший код. Код рассказывает историю, и, поскольку больше людей, чем только я, должны читать (и мы читаем больше кода, чем пишем), очень важно рассказать свою историю. красиво и блестяще, а не запутанно и двусмысленно. Кстати. во время написания моего последнего комментария к этому ответу я не учел то, что вы только что сказали об интерфейсах, являющихся языковой конструкцией. Ты прав. - person Alex Endris; 09.09.2014

Хорошей отправной точкой может стать эта статья: Почему я использую явную реализацию интерфейса в качестве метода реализации по умолчанию

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

ОБНОВЛЕНИЕ: в комментариях ниже OP заявляет, что явная реализация портит наследование, поскольку вы не можете переопределить реализацию интерфейса в производном типе. Я не уверен, правильно ли я понимаю проблему:

public interface IFooable
{
     void Foo();
     void FooAgain();
}

public class Blah: IFooable
{
     void IFooable.Foo()
     {
         Console.WriteLine("Hi from 'Blah'!");
     }

     void IFooable.FooAgain() {}
}

public class Bar: Blah, IFooable
{
     void IFooable.Foo()
     {
         Console.WriteLine("Hi from 'Bar'!");
     }
}

public static void Main()
{
   var fooList = new List<IFooable>();
   fooList.Add(new Blah());
   fooList.Add(new Bar());

   foreach (var fooable in fooList) //Outputs "Hi from 'Blah'!" / "Hi from 'Bar'!"
       fooable.Foo();

   Console.ReadLine();
}
person InBetween    schedule 08.09.2014
comment
Я уже читал это. Проблема в том, что вы можете точно так же аргументировать против этого. Лично я бы использовал явную реализацию интерфейса только тогда, когда это действительно необходимо, так как это также может испортить ситуацию, и у вас нет никакой выгоды от ее использования по сравнению с обычной реализацией. Но я действительно хотел бы услышать больше от обеих сторон. Зачем препятствовать этому или почему поощрять это. Что, на ваш взгляд, делает его достойным использования и что вы от этого получаете? Благодарю вас! - person Alex Endris; 08.09.2014
comment
@Qudeid Лично я использую явные реализации, когда интерфейс имеет тенденцию использоваться не такими прозрачными механизмами фреймворка. Типичным примером будет IConvertible. Я не видел так много кода, напрямую вызывающего метод этого интерфейса, обычно он используется через System.Convert Тем не менее я не совсем уверен, что есть правильный или неправильный способ сделать это, я думаю, это скорее стиль кодирования. - person InBetween; 08.09.2014
comment
Нет, нет правильного пути. Для меня это больше похоже на дискуссию о том, с какими проблемами вы можете столкнуться, когда пойдете тем или иным путем. Проблема, которую я вижу в сообщении выше, заключается в том, что кто-то указывает причину использования ЕГО, но он также опускает многое. Судя по комментариям внизу, похоже, что он проповедует. Возможно, мне следует отредактировать свой вопрос, что это должно быть скорее обсуждением, чем вопросом. - person Alex Endris; 08.09.2014
comment
@Qudeid - Объясните, что все испортили. Вы несколько раз упомянули об этом, но как-то рука махнула... Что вы имеете в виду? - person Erik Funkenbusch; 08.09.2014
comment
@ Эрик То, что я имею в виду, говоря об испорченных вещах, - это наследование. После того, как вы явно реализовали интерфейс, вы не можете наследовать от этого одного класса и переопределить реализацию. Очевидно, что есть обходной путь, как указано в статье codeproject, IIRC. напишите виртуальный метод, который имеет реализацию метода интерфейса, и вызывайте его только в реализации интерфейса. Но потом я удивляюсь, почему все хлопоты? Работа над чем-то, что вам, вероятно, даже не нужно. (Если, конечно, не возникнет конфликта имен, например, с IEnumerable/IEnumerable‹›. - person Alex Endris; 09.09.2014
comment
@Qudeid Я не уверен, что следую за тобой. Конечно, вы можете переопределить реализацию, вы просто снова явно реализуете интерфейс в производном типе. Я обновил свой ответ примером кода. Может быть, я неправильно вас понимаю. - person InBetween; 09.09.2014
comment
@Qudeid В реализациях интерфейса C#, явных или нет, всегда виртуальные. В CIL метод интерфейса помечен как virtual и abstract. Я не уверен, в чем ваша проблема с наследованием и явной реализацией. - person InBetween; 09.09.2014
comment
@ Эрик У тебя нет аргументов, но ты пытаешься возражать против меня. Объясните пожалуйста. - person Alex Endris; 09.09.2014
comment
@InBetween Нет. Явная реализация интерфейса не является виртуальной. Что вы делаете в своем коде, так это заставляете Bar дважды наследоваться от IFooable. Зачем вам это нужно, поскольку Бла уже является IFooable. В предоставленном вами коде уже указано, что он не виртуальный. Для меня это запах кода и совсем не обязательно. Только потому, что вы МОЖЕТЕ что-то сделать, не означает, что это хорошо или правильно. (По крайней мере, не в целом) - person Alex Endris; 09.09.2014
comment
@InBetween Кроме того, что делает этот код: void DoSmth(Blah blah) { ((IFooable)blah).Foo(); }? - person Alex Endris; 09.09.2014
comment
@Qudeid Вы ошибаетесь, явная реализация является виртуальной, но не переопределяемой. Чтобы переопределить его, вам нужен этот хак. CIL помечает его как virtual final. - person InBetween; 09.09.2014
comment
@InBetween извините, но это придирки. финал утверждает это. Это заканчивается там. Помещение интерфейса в производный класс снова, как вы сами утверждаете, является хаком. Конечно не красивый код. - person Alex Endris; 09.09.2014
comment
@Qudeid Конечно, это хороший код. В .NET framework полно подобных случаев. Просто взгляните на определение List<T>. Он реализует, среди прочего, IList<T>, ICollection<T> и IEnumerable<T>. Вы не можете получить больше избыточности: IList<T> реализует IColletion<T> и IEnumerable<T>, а ICollection<T> реализует IEnumerable<T>. Это 3 лишних IEnumerable<T> и 2 ICollection<T>. Ваша точка зрения немая. - person InBetween; 09.09.2014
comment
@Qudeid: StackOverflow — это сайт вопросов и ответов, а не дискуссионный форум. Большинство из нас здесь, чтобы помочь ответить на вопросы людей. Ваш вопрос изначально был не очень ясен, и теперь вы разделяете мнение о разнице между возможностью определить новую явную реализацию и возможностью переопределить явную реализацию родительского класса. У вас такое впечатление, как будто вы задали вопрос, чтобы спровоцировать спор. Вы можете подумать, является ли это подходящим местом для обсуждения такого рода. - person StriplingWarrior; 09.09.2014
comment
@StriplingWarrior Учитывая количество разработчиков, которые, возможно, читают это, да, это правильное место. - person Alex Endris; 09.09.2014
comment
@InBetween Существует разница между определенной структурой и кодами, используемыми для ваших особых нужд. То, что код хорош, субъективно, но не субъективно то, что у него проблемы с коммуникацией. Когда я вижу код, я спросил вас, что он делает, а потом смотрю Бла. Я вижу, что это явно реализовано, и считаю, что на этом все заканчивается. Зачем вам писать сложный код только для того, чтобы получить что-то работающее из коробки с виртуальным, когда вы неявно реализовали интерфейс? Считают, что. - person Alex Endris; 09.09.2014
comment
@Qudeid Я полностью согласен. Если бы у меня был выбор, я бы никогда так не поступил. Но я видел случай, когда нужно было переопределить поведение явно реализованного интерфейса, и это было сделано так, потому что другого выхода не было. Это возможно. Очевидно, что если вы создаете код с нуля и переопределение реализации является правдоподобным сценарием, я бы не стал этого делать. Тем не менее, явная реализация интерфейса является хорошим аргументом даже в этом сценарии. Защищенные виртуальные методы позволяют переопределить реализацию. Преимущества: расхламление публичного API, рефакторинг и т. д. - person InBetween; 09.09.2014
comment
@InBetween Не поймите меня неправильно. Я не говорю, что никогда не следует использовать явную реализацию интерфейса. Я хочу сказать, что нужно думать, какие выгоды вы получаете от этого. Если я рассмотрю мой изложенный выше сценарий, в чем преимущество использования явных интерфейсов по умолчанию? Конечно, по сути нет никакой разницы, если нет наследования (рантайма), если наследования мало и если используется только интерфейс. Но, как сказал StriplingWarrior, зачем превентивно использовать это, чтобы воспрепятствовать использованию конкретного класса? Для меня это запах. - person Alex Endris; 09.09.2014