при использовании интерфейса класс должен всегда строго реализовывать интерфейс - PullRequest
6 голосов
/ 28 сентября 2010

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

public interface IDoSomething
{
  void Method1(string operation, User user, string category)
  void Method2(string operation, User user)
  void Method3(string operation)
}

//Approach1
Class A: IDoSomething
{                              
  public void Method1(string operation, User user, string category)
  {
   //do some db logic here...
  }

  public void Method2(string operation, User user)
  {
    Method1(operation, user, "General");
  }

  public void Method3(string operation)
  {
    Method1(operation, User.GetDefaultUser(), "General");
  }
}

OR

//Approach2
Class A: IDoSomething
{                              
  void IDoSomething.Method1(string operation, User user, string category)
  {
   //do some logic here...
  }

  void IDoSomething.Method2(string operation, User user)
  {
    (this as IDoSomething).Method1(operation, user, "General");
  }

  void IDoSomething.Method3(string operation)
  {
    (this as IDoSomething).Method1(operation, User.GetDefaultUser(), "General");
  }
}

Ответы [ 4 ]

7 голосов
/ 28 сентября 2010

Фраза, которую вы ищете для описания второго подхода: явная реализация интерфейса . Лично я бы не использовал его большую часть времени. Полезно, если вам нужно по-разному реализовывать разные интерфейсы с одной и той же сигнатурой элемента или если вы хотите ограничить некоторые элементы до только , которые будут видны при работе с выражением типа интерфейса ... но может быть неприятно, если по какой-либо причине ваш вызывающий объект имеет конкретный тип и ему нужно вызвать некоторые методы, специфичные для типа, и некоторые методы интерфейса. Вы даже не можете увидеть явно реализованные методы в том же классе без приведения this к типу интерфейса.

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

One final note: явная реализация интерфейса также плохо работает с dynamic в C # 4 .

2 голосов
/ 28 сентября 2010

Подход 1 - это естественный способ реализации интерфейсов в C #. Если вы напишите библиотеку, это то, что ожидают ваши клиенты; если вы используете класс самостоятельно, вы экономите много каста при вызове методов.

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

  • реализация нескольких интерфейсов, совместно использующих одну и ту же сигнатуру метода (но требующих разных реализаций),
  • имена методов, которые имеют смысл для интерфейса, но могут вводить в заблуждение ваш класс.
0 голосов
/ 28 сентября 2010

Одна очень распространенная комбинация этих двух случаев - при реализации IEnumerable<T>.Поскольку IEnumerable<T> наследуется от IEnumerable, то вы должны реализовать как IEnumerable<T>.GetEnumerator(), так и IEnumerable.GetEnumerator().Так как они имеют другой тип возвращаемого значения, но совпадают со списком параметров (пустым), неявно может быть реализован только один не более.

Наиболее распространенный шаблон - реализация универсальной версии:

public IEnumerator<T> GetEnumerator()
{
  //some code that returns an appropriate object.
}
IEnumerator IEnumerable.GetEnumerator()
{
  return GetEnumerator();//return the result of the other call.
}

Теперь не нужно иметь неявное, или можно (с небольшим приведением в явную реализацию) иметь это наоборот.Однако это удобнее по следующим причинам:

  1. Мы, вероятно, предпочли бы, чтобы неявная версия вызывалась, поскольку она, вероятно, будет иметь лучшую безопасность типов и, возможно, лучшую эффективность, если будет обработан объект перечислителя.через его более явный интерфейс.
  2. Неявная версия будет обслуживать как вызывающий код, который хочет IEnumerator<T>, так и вызывающий код, который хочет IEnumerator (так как это неявное приведение), и, следовательно, является наиболее полезным.

Теперь это тот случай, когда мы вынуждены использовать явный интерфейс.В качестве примера случая, в котором мы могли бы выбрать, рассмотрим написание списка только для чтения, который заключает в себе List<T> и реализует IList<T>.

Все, что нам нужно здесь сделать, - это делегировать операции чтенияупакованный список и throw не поддерживаются для операций записи, например:

public T this[int idx]
{
  get
  {
    return _inner[idx];
  }
  set
  {
    throw new NotSupportedException("Read only list.");
  }
}
public int Count
{
  get
  {
    return _inner.Count;
  }
}
public void Add(T item)
{
  throw new NotSupportedException("Read only list.");
}
public bool IsReadOnly
  {
  get
  {
    return false;
  }
}

и так далее.Однако это не очень полезно для людей, использующих объект с конкретным типом, у нас есть целая группа членов, которые либо возвращают один и тот же результат (IsReadOnly), либо всегда выдают исключение.Хотя код, использующий класс через его интерфейс, должен иметь возможность вызывать эти методы и свойства, это не имеет никакого значения для конкретного класса.Следовательно, мы можем сделать следующее:

public T this[int idx]
{
  get
  {
    return _inner[idx];
  }
}
T IList<T>.this[int index]
{
  get { return this[index]; }
  set { throw new NotSupportedException("Collection is read-only."); }
}
public int Count
{
  get
  {
    return _inner.Count;
  }
}
void ICollection<T>.Add(T item)
{
  throw new NotSupportedException("Read only list.");
}
bool ICollection<T>.IsReadOnly
  {
  get
  {
    return false;
  }
}

Теперь, пока мы полностью реализуем IList<T>, мы также предлагаем гораздо более чистый и более полезный интерфейс (в общем, а не в C # специфическом смысле «интерфейса») для пользователей.конкретного класса.

Обратите внимание, что это основано именно на причине, по которой обычно имеет смысл быть неявным: мы сделали все эти члены более неудобными для пользователя конкретного класса, чтобы получить (такойкод должен был бы привести к IList<T> сначала).Это хорошо, так как единственный результат такого неуклюжего кода бессмыслен или опасен, а делать плохие идеи труднее - это хорошо.Если бы такая неловкость только мешала, это было бы другое дело.

0 голосов
/ 28 сентября 2010

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

A item = new A();
item.Method2(operation, user); // will not compile
(item As IDoSomething).Method2(operation, user); // works well

Этот подход полезен, если вы не хотите, чтобы реализации интерфейса по какой-то причине «загромождали» список членов IntelliSense.

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

...