Стандарт кодирования интерфейса / абстрактного класса - PullRequest
7 голосов
/ 25 января 2011

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

Ответы [ 5 ]

15 голосов
/ 25 января 2011

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

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

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

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

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

В общем, предоставленное руководство ДОЛЖНО определять классы по интерфейсам .И снова Кшиштоф комментирует:

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

Во втором руководстве утверждается, что один ДОЛЖЕН использовать абстрактные классы вместо интерфейсов для отделения контракта от реализаций .Дело в том, что правильно разработанные абстрактные классы по-прежнему допускают ту же степень разделения между контрактом и реализацией, что и интерфейсы.Таким образом, личная точка зрения Брайана Пепина такова:

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

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

3 голосов
/ 25 января 2011

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

1 голос
/ 25 января 2011

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

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

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

Именно поэтому я обычно связываю абстрактный класс с интерфейсом.

0 голосов
/ 25 января 2011

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

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

РЕДАКТИРОВАТЬ: обновлено с примером

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

// 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 голосов
/ 25 января 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...