Подтвердить что ты не робот

Почему у каждого класса в инфраструктуре .NET нет соответствующего интерфейса?

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

Так как насмешливые фреймворки, такие как Moq, лучше всего работают, когда говорят, чтобы издеваться над интерфейсом, теперь я реализую интерфейс практически для каждого класса, который я создаю b/c, скорее всего, мне придется издеваться над этим во время теста. Ну, а программирование на интерфейс - хорошая практика, так или иначе.

Иногда мои классы принимают зависимости от .Net-классов (например, FileSystemWatcher, DispatcherTimer). Было бы здорово в этом случае иметь интерфейс, поэтому я мог бы зависеть от IDispatcherTimer, чтобы иметь возможность передать его макет и имитировать его поведение, чтобы проверить, правильно ли реагирует моя система.

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

Вот такой адаптер для DispatcherTimer и соответствующего интерфейса:

 using System;
using System.Windows.Threading;

public interface IDispatcherTimer
{
    #region Events

    event EventHandler Tick;

    #endregion

    #region Properties

    Dispatcher Dispatcher { get; }

    TimeSpan Interval { get; set; }

    bool IsEnabled { get; set; }

    object Tag { get; set; }

    #endregion

    #region Public Methods

    void Start();

    void Stop();

    #endregion
}

/// <summary>
/// Adapts the DispatcherTimer class to implement the <see cref="IDispatcherTimer"/> interface.
/// </summary> 
public class DispatcherTimerAdapter : DispatcherTimer, IDispatcherTimer
{
}

Хотя это еще не конец света, интересно, почему разработчики .Net не заняли минуту, чтобы заставить их классы реализовать эти интерфейсы с самого начала. Это меня очень удивляет, так как теперь есть большой толчок для хороших практик изнутри Microsoft.

Есть ли у кого-нибудь (возможно, внутри) информация, почему это противоречие существует?

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

Это старая дискуссия о том, что TDDers считают, что в BCL слишком мало Seams, в то время как Microsoft, как правило, очень консервативна с предоставляемыми ими швами. Многие реализации BCL в значительной степени зависят от внутренних и закрытых классов, которые действительно вредят тестируемости.

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

В то время как стало лучше, крупнейшим аргументом Microsoft в отношении предоставления более открытого BCL является то, что он накладывает несколько усилий на их усилия в области развития:

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

Я не говорю, что полностью согласен с этими аргументами, но те, которые чаще всего предоставляются Microsoft.

Ответ 3

Интерфейсы представляют собой контракты, на которые согласны каждый реализующий класс. Это способ управления абстракцией и частью инструментов языка, используемого для построения объектно-ориентированных систем. Если единственная причина для внедрения интерфейса - это легкому издеваться над классом, тогда интерфейса не должно быть. Mocking - это концепция, имеющая смысл только для разработчиков системы. Тот факт, что класс не может насмехаться, не является признаком плохого дизайна. Если мы будем следовать той же логике, то зачем нам запечатанное ключевое слово? Зачем вам нужно, чтобы кто-то унаследовал кого-то, почему бы вам сделать метод не виртуальным, чтобы остановить переопределение? Потому что эта часть объектно-ориентированного дизайна. И вы не создаете дизайн, основанный на способностях насмешливых фреймворков. Короче говоря, наличие интерфейса для каждого класса было бы смешно.

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

P.S. если вы используете TypeMock, вы можете даже высмеять статические и закрытые классы.

Ответ 4

Одной из основных причин неиспользования интерфейсов по умолчанию является будущая проверка вашего API; невозможно расширить интерфейс. Вы можете создавать только новые интерфейсы. Это означает, что вы получите IFileWatcher1, IFileWatcher2, IFileWatcher3 (просто посмотрите на COM, чтобы увидеть это в действии). Это сильно загрязняет API. Классы (абстрактные и не абстрактные) могут быть расширены.

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

Ответ 5

Интерфейсы имеют таблицы слабости - виртуальные функции. Для каждого метода/свойства интерфейса система резервирует указатель в специальном блоке памяти - виртуальную таблицу класса. Это замедляет работу функций и потребляет память. Поэтому вы должны использовать только интерфейсы, где может существовать различная реализация. В противном случае вы ставите под угрозу производительность.