Зачем приводить к интерфейсу? - PullRequest
35 голосов
/ 06 марта 2009

В Программировании Джесси Либерти на C # (стр. 142) он приводит пример, где он приводит объект к интерфейсу.

 interface IStorable
 {
    ...
 }

 public class Document : IStorable
 {
    ...
 }

 ...
 IStorable isDoc = (IStorable) doc;  
 ...

Какой смысл в этом, особенно если класс объекта в любом случае реализует интерфейс?

EDIT1: чтобы уточнить, меня интересует причина приведения (если есть) , , а не причина реализации интерфейсов. Кроме того, эта книга является его первым изданием 2001 года (на основе C # 1, поэтому этот пример может оказаться неуместным для более поздних версий C #).

EDIT2: я добавил некоторый контекст в код

Ответы [ 11 ]

21 голосов
/ 06 марта 2009

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

16 голосов
/ 06 марта 2009

Существует только одна причина, когда вам действительно нужно приведение: когда документ имеет базовый тип реального объекта, который реализует IStorable. Позвольте мне объяснить:

public class DocBase
{
  public virtual void DoSomething()
  {

  }
}

public class Document : DocBase, IStorable
{
  public override void DoSomething()
  {
    // Some implementation
    base.DoSomething();
  }

  #region IStorable Members

  public void Store()
  {
    // Implement this one aswell..
    throw new NotImplementedException();
  }

  #endregion
}

public class Program
{
  static void Main()
  {
    DocBase doc = new Document();
    // Now you will need a cast to reach IStorable members
    IStorable storable = (IStorable)doc;
  }
}

public interface IStorable
{
  void Store();
}
15 голосов
/ 06 марта 2009

Если объект реализует интерфейс явно (public void IStorable.StoreThis(...)), то приведение является самым простым способом на самом деле достичь членов интерфейса.

13 голосов
/ 06 марта 2009

Я не уверен, в каком контексте пример был приведен в книге. Но, как правило, вы можете вводить объект для интерфейса для достижения множественного наследования. Я привел пример ниже.

public interface IFoo
{
     void Display();
}
public interface IBar
{
     void Display();
}

public class MyClass : IFoo, IBar
{
    void IBar.Display()
    {
        Console.WriteLine("IBar implementation");
    }
    void IFoo.Display()
    {
        Console.WriteLine("IFoo implementation");
    }
}

public static void Main()
{
    MyClass c = new MyClass();
    IBar b = c as IBar;
    IFoo f = c as IFoo;
    b.Display();
    f.Display();
    Console.ReadLine();
}

Это будет отображать

Внедрение IBar
Реализация IFoo

10 голосов
/ 06 марта 2009

Сложно сказать без контекста. Если переменная doc объявлена ​​как тип, реализующий интерфейс, то приведение является избыточным.

Какую версию книги вы читаете? Если это «Программирование на C # 3.0», я посмотрю сегодня вечером, когда буду дома.

РЕДАКТИРОВАТЬ: Как мы уже видели в ответах, здесь есть три потенциальных вопроса:

  • Почему приведено утверждение, показанное в вопросе? (Ответ: вам не нужно, если doc имеет соответствующий тип времени компиляции)
  • Почему всегда целесообразно явно приводить к реализованному интерфейсу или базовому классу? (Ответ: явная реализация интерфейса, как показано в другом ответе, а также для выбора менее специфичной перегрузки при передаче приведенного значения в качестве аргумента.)
  • Зачем вообще использовать интерфейс? (Ответ: работа с типом интерфейса означает, что вы в меньшей степени подвержены изменениям конкретного типа в дальнейшем.)
3 голосов
/ 06 марта 2009

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

На самом деле «приведение» (с использованием синтаксиса (T)) не имеет никакого смысла, поскольку C # автоматически обрабатывает преобразования (приведение к родительскому типу) (в отличие от F #, например).

2 голосов
/ 06 марта 2009

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

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

Вот простой пример:

public interface IText
{
   string Text { get; }
}

public interface ISuperDooper
{
   string WhyAmISuperDooper { get; }
}

public class Control
{
   public int ID { get; set; }
}

public class TextControl : Control, IText
{
   public string Text { get; set; }
}

public class AnotherTextControl : Control, IText
{
   public string Text { get; set; }
}

public class SuperDooperControl : Control, ISuperDooper
{
   public string WhyAmISuperDooper { get; set; }
}

public class TestProgram
{
   static void Main(string[] args)
   {
      List<Control> controls = new List<Control>
               {
                   new TextControl
                       {
                           ID = 1, 
                           Text = "I'm a text control"
                       },
                   new AnotherTextControl
                       {
                           ID = 2, 
                           Text = "I'm another text control"
                       },
                   new SuperDooperControl
                       {
                           ID = 3, 
                           WhyAmISuperDooper = "Just Because"
                       }
               };

       DoSomething(controls);
   }

   static void DoSomething(List<Control> controls)
   {
      foreach(Control control in controls)
      {
         // write out the ID of the control
         Console.WriteLine("ID: {0}", control.ID);

         // if this control is a Text control, get the text value from it.
         if (control is IText)
            Console.WriteLine("Text: {0}", ((IText)control).Text);

         // if this control is a SuperDooperControl control, get why
         if (control is ISuperDooper)
            Console.WriteLine("Text: {0}", 
                ((ISuperDooper)control).WhyAmISuperDooper);
      }
   }
}

запуск этой маленькой программы даст вам следующий вывод:

ID: 1

Текст: я текстовый элемент управления

ID: 2

Текст: я другой текстовый элемент управления

ID: 3

Текст: Просто потому что

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

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

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

2 голосов
/ 06 марта 2009

Здесь много хороших ответов, но я не думаю, что они отвечают, ПОЧЕМУ вы на самом деле ХОТИТЕ использовать максимально ограниченный интерфейс.

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

Допустим, вы хотите кнопку и размещаете ее на экране. Вы получаете кнопку, переданную или из другой функции, например:

Button x=otherObject.getVisibleThingy();
frame.add(x);

Вы знаете, что VisibleThingy - это кнопка, она возвращает кнопку, поэтому здесь все круто (не требуется приведение).

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

Поскольку вам НУЖНЫ только методы в Компоненте (родительский элемент для кнопки и переключателя, который мог бы быть интерфейсом - то же самое для наших целей), если вы написали эту первую строку следующим образом:

Component x=(Component)otherObject.getVisibleThingy();

Тебе не пришлось бы что-либо реорганизовывать - это бы просто сработало.

Это очень простой случай, но он может быть гораздо более сложным.

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

1 голос
/ 06 марта 2009

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

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

«Документ» не относится к типу «IStorable», поэтому начинающим было бы странно видеть, что он назначается для isDoc. При явном приведении автор (книги и кода) говорит, что документ может быть преобразован в объект IStorable, но он НЕ ЖЕ, как объект IStorable.

0 голосов
/ 09 марта 2009

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

См. Следующую статью для получения дополнительной информации: Интерфейсы

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