Использование интерфейсов C# при максимальном повторном использовании кода

Итак, скажем, у меня был класс C #,

class Foo : Bar, IBar, IBar2
{

}

... где Bar — класс, а IWhatever и IFine — интерфейсы. Я планирую использовать аналогичную реализацию IWhatever и IFine в нескольких классах. Единственный разумный способ инкапсулировать этот код и повторно использовать его во всех классах — создать класс Whatever, наследуемый от IWhatever, и Fine, наследуемый от IFine и сделайте их членами класса, который реализует эти интерфейсы, а затем вызовите их члены в членах, реализованных из интерфейсов, например так:

class Foo : Bar, IWhatever, IFine
{
   IWhatever mWhatever;
   IFine mFine;
   Foo()
   {
      mWhatever = new Whatever();
      mFine = new Fine();
   }
   // from IWhatever
   void Kick()
   {
      mWhatever.Kick();
   }
   // from IFine
   void Punch()
   {
      mFine.Punch();
   }
}

Я делаю это правильно? Есть ли способ лучше?


person fordeka    schedule 14.07.2012    source источник


Ответы (3)


Когда вы пытаетесь реализовать одни и те же несколько реализаций во многих классах с помощью наследования, это запах дизайна. Отсутствие множественного (классового) наследования в C# на самом деле является достоинством, а не недостатком в этом отношении, поскольку оно применяет принцип единственной ответственности.

Подумайте об этом так: два типа абстракций, доступных в C#, имеют две взаимодополняющие формы представления реальных сущностей.

  • Абстрактный класс: "Является ли"
  • Интерфейс: «Делает что-то» или «Имеет такие-то качества/поведение»

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

Будьте осторожны с использованием наследования. Помните мудрую мудрость GOF:

Предпочитайте композицию объектов наследованию.

Итак, возвращаясь к проблеме, которую вы пытаетесь решить, было бы лучше сделать это:

public class Foo : Bar
{
   public IWhatever Whatever
   {
      get;
      private set;
   }

   public IFine Fine
   {
      get;
      private set;
   }

   public Foo(IWhatever whatever, IFine fine)
   {
      Whatever = whatever;
      Fine = fine;
   }
}

Теперь используйте контейнер IOC для внедрения конкретных реализаций этих интерфейсов. Это моя скромная рекомендация.

person Jacobs Data Solutions    schedule 14.07.2012
comment
Спасибо, я подумаю об этом. - person fordeka; 14.07.2012

Вы можете сделать его более общим:

class Foo<TWhat, TFine> : Bar, IWhatever, IFine
    where TWhat : IWhatever, new()
    where TFine : IFine, new()
{
   IWhatever mWhatever;
   IFine mFine;
   Foo()
   {
      mWhatever = new TWhat();
      mFine = new TFine();
   }
   // from IWhatever
   public void Kick()
   {
      mWhatever.Kick();
   }
   // from IFine
   public void Punch()
   {
      mFine.Punch();
   }
}

Таким образом, Foo можно заставить использовать любые классы, которые реализуют IWhatever и IFine, а не только Whatever и Fine.

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

person royh    schedule 14.07.2012

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

interface MBar {} static class BarMethods {
  public static void M(this MBar self) { ... }
}

class Foo : MBar { ... }

Всю конструкцию interface MBar {} static class BarMethods { ... } можно рассматривать как единый «модуль», например module MBar { ... }. Вот почему я сохранил такое форматирование.

person Jordão    schedule 14.07.2012