2011-01-25 6 views
6

Я заметил предложенный стандарт кодирования C#, в котором говорилось: «Попробуйте предложить интерфейс со всеми абстрактными классами». Кто-нибудь знает причину этого?Стандарт кодирования интерфейса/абстрактного класса

+4

Можете ли вы предоставить ссылку, в которой вы видели это предложение? –

+0

@Jay: Google приводит меня сюда: http://docs.google.com/viewer?a=v&q=cache:reQ1URsy3X0J:www.danrigsby.com/Files/csharpcodingstandards.doc+%22Try+to+offer+an+interface + с + все + абстрактный + классы% 22 & гл = еп & гЛ = нам & PID = бл & srcid = ADGEESi9ylZVo1InFp604CL926OHZ_QLr4taUT6O0DaojgsrS__V7ID4pUJw7gtdp5s0u0NwQYmAAjMILDxJsphtH_XmeFmYhunknO8UxD5il580OTtSD-yl94xtz0uw6VhUycN5eVvv и сиг = AHIEtbQyKOvKQE69l_EVLO_3XNqJLeCKFw –

+0

и предполагая, что директивы я связан выше, по сути те же, что вы прочитали, самую первую точку зрения, высказанную в разделе «Интерфейсы раздел беспокоит меня еще больше: «Всегда предпочитайте интерфейсы над абстрактными классами». И это рекомендуется еще сильнее, чем руководство, которое вы цитируете. –

ответ

15

.NET Framework Design Guidelines Есть несколько интересных вещей, которые можно сказать о интерфейсах и абстрактных классах.

В частности, они отмечают, что интерфейсы имеют главный недостаток в том, чтобы быть менее гибкими, чем классы, когда дело доходит до эволюции API. Когда вы отправляете интерфейс, его члены фиксируются навсегда, и любые дополнения нарушают совместимость с существующими типами, реализующими этот интерфейс. В то время как доставка класса предлагает гораздо большую гибкость. Участники могут быть добавлены в любое время, даже после отправки исходной версии, если они не абстрактны. Любые существующие производные классы могут продолжать работать без изменений. В качестве примера приведен абстративный класс System.IO.Stream, представленный в Framework. Первоначально он был отправлен без поддержки тайм-аутов ожидающих операций ввода-вывода, но версия 2.0 смогла добавить участников, которые поддерживали эту функцию, даже из существующих подклассов.

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

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

Я часто слышу, как люди говорят, что интерфейсы определяют контракты. Я считаю, что это опасный миф. Интерфейсы сами по себе не указывают многое из синтаксиса, необходимого для использования объекта. Миф о взаимодействии с контрактом заставляет людей поступать неправильно, пытаясь отделить контракты от реализации, что является отличной инженерной практикой. Интерфейсы изолируют синтаксис от реализации, что не так полезно, и миф дает ложное представление о правильной инженерии. На самом деле контракт является семантикой, и на самом деле они могут быть хорошо выражены с некоторой реализацией.

В общем, руководстве предоставленное Поддерживает определение классов через интерфейсы. Опять же, Krzysztof комментирует:

В ходе трех версий .NET Framework я говорил об этом руководстве с несколькими разработчиками в нашей команде. Многие из них, в том числе те, кто первоначально не соглашался с руководством, заявили, что сожалеют о том, что отправили API в качестве интерфейса. Я не слышал ни одного случая, когда кто-то сожалел, что отправил класс.

Второй принцип утверждает, что один ЗАПРЕЩАЕТСЯ использовать абстрактные классы вместо интерфейсов разъединить контракт от реализации. Дело в том, что правильно разработанные абстрактные классы по-прежнему допускают такую ​​же степень развязки между контрактом и реализацией как интерфейсы. Таким образом, персональная перспектива Брайана Пепина:

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

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

+0

+1 Мне больше нравится ваше объяснение. Я все еще пытаюсь понять, почему Glav сказал, что делает тестирование с Rhino проще. –

+0

@ Харви: Спасибо. Я даже не знаю, что такое Glav, потому что я никогда не использовал ни один из этих тестовых издевательских инструментов. Вот почему я убедился, что выясню, что могут быть исключения, основанные на конкретных причинах. –

3

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

+0

Глав прав. По крайней мере, с Rhino Mocks, тестирование конкретных методов - это PITA. Таким образом, наличие абстрактного класса, реализующего интерфейс, дает гораздо большую возможность проверки. – Simone

0

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

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

1

Я всегда понимал интерфейс-Driven Design (ИУС), чтобы включать в себя следующие шаги в создании конкретного класса (в его чистом виде, для нетривиальных классов):

  1. Создание интерфейса для описания свойств и поведение, которое должны проявлять ваши объекты, но не то, как они должны функционировать.
  2. Создайте абстрактный базовый класс в качестве основной реализации интерфейса. Внедрите любую функциональность, требуемую интерфейсом, но которая вряд ли будет отличаться между конкретными реализациями. Также укажите соответствующие варианты по умолчанию (virtual) для членов, которые маловероятны (но возможны) для изменения. Вы также можете предоставить соответствующие конструкторы (что-то не возможно на уровне интерфейса). Отметьте все остальные элементы интерфейса как абстрактные.
  3. Создайте свой конкретный класс из абстрактного класса, переопределив подмножество элементов, первоначально определенных интерфейсом.

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

Вот почему я, как правило, пару абстрактного класса с интерфейсом.

+0

Неясно, какое преимущество интерфейса находится под этой моделью. Какие преимущества вы получаете от создания интерфейса сначала, а не просто для создания абстрактного базового класса в качестве его реализации? –

+0

Если бы мы смогли это сделать все время! – XIVSolutions

+0

@Cody Это очень пуристское отношение. Если вы согласны с тем, что единственный (правильный) способ продиктовать поведение - через интерфейс, то, начиная с абстрактного класса, он рассматривается как слабый подход. Кроме того, интерфейс представляет элементы, поскольку они предназначены для вызова, тогда как абстрактный класс представляет их, поскольку они предназначены для реализации/переопределения - тонкая разница в семантике. –

0

Разработка, основанная на испытаниях (TDD) является одной из основных причин, почему вы хотели бы это сделать. Если у вас есть класс, который напрямую зависит от вашего абстрактного класса, вы не можете его протестировать без написания подкласса, который может быть создан в ваших модульных тестах. Если, однако, ваш зависимый класс зависит только от интерфейса, вы можете предоставить «пример» этого легко, используя насмешливую структуру, такую ​​как Rhino Mocks, NMock и т. Д.

В конечном счете, я думаю, что это будет не так, как вы отправляете свой товар. Мы только когда-либо поставляем двоичные файлы, и клиенты никогда не расширяют нашу работу. Внутри у нас есть интерфейсы для почти всего, поэтому классы могут быть полностью изолированы для модульного тестирования.Это дает огромные преимущества для рефакторинга и регрессионного тестирования!

EDIT: обновляется с примером

Рассмотрим следующий код в модульном тесте:

// doesn't work - can't instantiate BaseClass directly 
var target = new ClassForTesting(new BaseClass());  

// where we only rely on interface can easily generate mock in our tests 
var targetWithInterface = new ClassForTestingWithInterface(MockRepository.GenerateStub<ISomeInterface>()); 

где абстрактный версия класс:

// dependent class using an abstract class 
public abstract class BaseClass 
{ 
    public abstract void SomeMethod(); 
} 

public class ClassForTesting 
{ 
    public BaseClass SomeMember { get; private set; } 

    public ClassForTesting(BaseClass baseClass) 
    { 
     if (baseClass == null) throw new ArgumentNullException("baseClass"); 
     SomeMember = baseClass; 
    } 
} 

и тот же материал, но с использованием интерфейс:

public interface ISomeInterface 
{ 
    void SomeMethod(); 
} 

public abstract class BaseClassWithInterface : ISomeInterface 
{ 
    public abstract void SomeMethod(); 
} 

public class ClassForTestingWithInterface 
{ 
    public ISomeInterface SomeMember { get; private set; } 

    public ClassForTestingWithInterface(ISomeInterface baseClass) {...} 
} 
+0

Как насчет этого? Кажется, он может макет абстрактного класса с использованием Rhino http://stackoverflow.com/questions/620894/mock-abstract-class-default-behaviour-with-rhino –

Смежные вопросы