Когда некоторые методы не будут использоваться / не реализованы, использовать интерфейс или абстрактный класс? - PullRequest
2 голосов
/ 12 февраля 2011

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

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

Ответы [ 7 ]

2 голосов
/ 12 февраля 2011

В общем, ответ на вопрос, использовать ли наследование или интерфейс, можно получить, если подумать об этом следующим образом:

Если подумать о гипотетических классах реализации,случай, когда эти типы являются тем, что я описываю, или это случай, когда эти типы могут быть или могут делать то, что я описываю?

Рассмотрим, например, интерфейс IEnumerable<T>.Классы, которые реализуют IEnumerable<T>, являются разными классами.Они могут быть перечисляемой структурой, но они принципиально являются чем-то другим (List<T> или Dictionary<TKey, TValue> или запросом и т. Д.)

С другой стороны, посмотрите наSystem.IO.Stream класс.Хотя классы, которые наследуются от этого абстрактного класса, различны (например, FileStream и NetworkStream), оба они в основном являются потоками - просто разными видами.Функциональность потока лежит в основе того, что определяет эти типы, а не просто описывает часть типа или набор поведений, которые они предоставляют.

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

Также имейте в видучто все еще возможно обеспечить некоторые основные функциональные возможности интерфейса с помощью методов расширения.Хотя это, строго говоря, не накладывает никакого фактического кода экземпляра на интерфейс (поскольку это невозможно), вы можете имитировать его.Вот как функции запросов LINQ-to-Objects работают на IEnumerable<T> посредством статического класса Enumerable, который определяет методы расширения, используемые для запроса общих экземпляров IEnumerable<T>.

Как примечаниеВам не нужно бросать NotImplementedException с.Если вы определяете функцию или свойство как abstract, то вам не нужно (и, фактически, не можете) предоставить тело функции для него в абстрактном классе;наследующие классы будут вынуждены предоставлять тело метода. Они могут выдавать такое исключение, но вам не о чем беспокоиться (и это верно и для интерфейсов).

1 голос
/ 15 февраля 2011

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

Было бы очень хорошо, если бы .net позволял интерфейсу включать реализации по умолчанию для свойств, методов и событий, которые не использовали какие-либо поля объекта. С технической точки зрения, я думаю, что такое можно было бы сделать без особых сложностей, имея для каждого интерфейса список записей vtable по умолчанию, которые можно использовать с реализациями, которые не определяют все слоты vtable. К сожалению, ничего подобного нет в .net.

1 голос
/ 12 февраля 2011

Лично я думаю, что это зависит от того, что определяет «тип».

Если вы определяете набор поведений, я бы порекомендовал интерфейс.

Если, с другой стороны, тип действительно определяет «тип», то я бы предпочел абстрактный класс. Я бы порекомендовал оставить методы абстрактными вместо обеспечения пустого поведения.


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

Если это так, вам следует рассмотреть возможность разбиения этого на несколько абстрактных классов или интерфейсов. Наличие «неподходящих» методов в базовом классе / интерфейсе действительно является нарушением принципа подстановки Лискова и признаком недостатка проекта.

1 голос
/ 12 февраля 2011

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

0 голосов
/ 12 февраля 2011

@ Earlz Я думаю, ссылаясь на это: Note, even though all of these functions and such may be defined, it does not mean they will all be used in all use cases. напрямую связано с лучшим способом «атаковать» эту проблему.

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

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

0 голосов
/ 12 февраля 2011

Другой шаблон, который работает в некоторых ситуациях, - это создание базового класса, который не является абстрактным. У него есть набор открытых методов, которые определяют API. Каждый из них вызывает защищенный метод, который является Overideable. Это позволяет производному классу выбирать, какие методы ему нужно реализовать. Так например

 public void AddUser(object user)
    {
        AddUserCore(user);
    }

    protected virtual void AddUserCore(object user)
    {
        //no implementation in base
    }
0 голосов
/ 12 февраля 2011

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

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

Кроме того, вместо того, чтобы выдавать NotImplementedException, вы должны объявить свой метод / свойство с ключевым словом abstract, чтобы все наследники должны были предоставить реализацию.

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