Причина использования ОБА абстрактных классов и интерфейсов? (Абстрактный класс реализует интерфейс) - PullRequest
22 голосов
/ 31 мая 2011

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

  // This describes a person....
  public interface IPerson
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int BasePay { get; set; }
    public string Address { get; set; }
  }

  // And so does this, but it also uses the interface....
  public abstract class Person : IPerson
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int BasePay { get; set; }
    public string Address { get; set; }
  }

  // This uses both ?!
  public class CoalMiner : Person, IPerson
  {
    public CoalMiner()
    {
      BasePay = 10000;
    }
  }

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

Ответы [ 14 ]

17 голосов
/ 31 мая 2011

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

Для контрактов должны использоваться интерфейсы - все люди всегда Person, поэтому абстрактный класс здесь имеет больше смысла. Могут быть добавлены интерфейсы для поведения, привязанного к конкретным типам людей, или для возможности использования Person определенным образом при выполнении контракта (то есть: Person : IComparable<Person>).

10 голосов
/ 31 мая 2011

Наличие интерфейса IPerson и базового класса Person обеспечивает вам определенные свободы, если вы передаете объекты в интерфейсе IPerson, а не в базовом классе Person.

Базовые классы имеют тенденцию реализовывать общий код, который должен использоваться всеми потомками этого базового класса. Это хорошо, если вы этого хотите, но можно столкнуться с ситуацией, когда требуется совершенно другая реализация IPerson, когда базовый класс Person вообще не используется. Теперь у вас есть 2 иерархии классов, которые имеют IPerson общего, и все еще работает. Вы не сможете сделать это только с Person.

Еще одна веская причина для избыточности постоянного наличия интерфейса - взаимодействие с COM.

4 голосов
/ 31 мая 2011

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

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

Явное упоминание интерфейса иногда делает его более читабельным. Документация MSDN часто делает это, например, показывая, что List<> реализует как ICollection<>, так и IList<>, хотя IList<> является производным от ICollection<>.

3 голосов
/ 31 мая 2011

Интерфейсы определяют контракт для поведения, так что это имеет смысл только в том случае, если у вас есть достаточное поведение (помимо простых средств доступа к свойствам), которое не-Персона может захотеть реализовать IPerson. Например, если IPerson может HoldConversation(Stream input, Stream output), то у вас может быть TuringTestCandidate, который реализует IPerson, но не является производным от Person.

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

3 голосов
/ 31 мая 2011

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

2 голосов
/ 24 июля 2015

Я фанат обоих в правильной ситуации. Почему?

Даже если вам нужен только интерфейс для какого-либо типа IOC / DI, он не предоставляет общих функций.Вы можете сделать это для Inject, чтобы базовая функциональность была покрыта общим абстрактным базовым классом.Тогда только абстрактные / виртуальные методы по мере необходимости.

Это самое лучшее ИМХО.Особенно в многоцелевом решении.

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

2 голосов
/ 31 мая 2011

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

Если бы вы реализовывали методы для объектов с двумя руками и двумя ногами -> IThingWithArmsAndLegs::DoTheHokeyPokey(), это могло бы быть хорошим использованием интерфейса. Тогда этот интерфейс мог бы быть общим для Person : IThingWithArmsAndLegs и Alien : IThingWithArmsAndLegs.

1 голос
/ 24 июля 2015

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

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

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

1 голос
/ 05 марта 2012

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

1 голос
/ 31 мая 2011

Хотя NEED для CoalMiner не имеет интерфейса IPerson, я знаю, что некоторые люди предпочитают это, поэтому очевидно, что тип реализует интерфейс. Это, как говорится, я не это очень полезно, как это.

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

public interface ICustomer {}

public abstract class SapEntity {}
public abstract class NHibernateEntity {}    

public class SapCustomer : SapEntity, ICustomer {}
public class NHibernateCustomer : NHibernateEntity, ICustomer {}

public class CustomerProcessor
{
   public ICustomer GetCustomer(int customerID)
   {
      // business logic here
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...