В общем, ответ на вопрос, использовать ли наследование или интерфейс, можно получить, если подумать об этом следующим образом:
Если подумать о гипотетических классах реализации,случай, когда эти типы являются тем, что я описываю, или это случай, когда эти типы могут быть или могут делать то, что я описываю?
Рассмотрим, например, интерфейс IEnumerable<T>
.Классы, которые реализуют IEnumerable<T>
, являются разными классами.Они могут быть перечисляемой структурой, но они принципиально являются чем-то другим (List<T>
или Dictionary<TKey, TValue>
или запросом и т. Д.)
С другой стороны, посмотрите наSystem.IO.Stream
класс.Хотя классы, которые наследуются от этого абстрактного класса, различны (например, FileStream
и NetworkStream
), оба они в основном являются потоками - просто разными видами.Функциональность потока лежит в основе того, что определяет эти типы, а не просто описывает часть типа или набор поведений, которые они предоставляют.
Часто вы найдете, что полезно делать оба;определите интерфейс, который определяет ваше поведение, а затем абстрактный класс, который реализует его и обеспечивает основные функциональные возможности.Это позволит вам, при необходимости, иметь лучшее из обоих миров: абстрактный класс для наследования от того, когда функциональность является основной, и интерфейс для реализации, когда это не так.
Также имейте в видучто все еще возможно обеспечить некоторые основные функциональные возможности интерфейса с помощью методов расширения.Хотя это, строго говоря, не накладывает никакого фактического кода экземпляра на интерфейс (поскольку это невозможно), вы можете имитировать его.Вот как функции запросов LINQ-to-Objects работают на IEnumerable<T>
посредством статического класса Enumerable
, который определяет методы расширения, используемые для запроса общих экземпляров IEnumerable<T>
.
Как примечаниеВам не нужно бросать NotImplementedException
с.Если вы определяете функцию или свойство как abstract
, то вам не нужно (и, фактически, не можете) предоставить тело функции для него в абстрактном классе;наследующие классы будут вынуждены предоставлять тело метода. Они могут выдавать такое исключение, но вам не о чем беспокоиться (и это верно и для интерфейсов).