Каковы некоторые преимущества использования интерфейса в C #? - PullRequest
21 голосов
/ 24 июня 2009

Несколько лет назад я был вовлечен в программный проект на работе и был вынужден быстро изучать C #. У меня слабый опыт программирования (классический ASP).

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

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

Спасибо Kevin

Ответы [ 10 ]

16 голосов
/ 24 июня 2009

Интерфейс говорит, как что-то должно работать. Думайте об этом как о контракте или шаблоне. Это ключ к таким вещам, как Inverson of Control или Depenancy Injection.

Я использую Карту структуры в качестве контейнера IoC. Это позволяет мне определить интерфейс для всех моих классов. Где вы могли бы сказать

Widget w = new Widget();

Я бы сказал

IWidget w = ObjectFactory.GetInstance<IWidget>();

Это очень мощно, потому что мой код не обязательно говорит, что такое Widget. Он просто знает, что может делать виджет на основе интерфейса IWidget.

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

Кроме того, поскольку я использую контейнер IoC, я могу предоставить в приложение новые интересные функции, такие как написание трех различных способов кэширования данных. Я могу иметь локальный кеш разработчика, используя такой инструмент, как Lucene.NET для локального кэша. У меня может быть сервер разработки, использующий кеш .NET, который отлично работает на одном компьютере. И тогда у меня может быть третий вариант для моих производственных серверов, использующих слой кеша, такой как MemCache Win32 или Velocity. Пока все три реализации кэширования соответствуют одному и тому же интерфейсу, их фактическая реализация не касается меня (или моего кода) вообще. Я просто прошу StructureMap перейти к реализации текущей среды, а затем приступить к работе.

Если вы вообще следуете Dependency Injection, то здесь вам пригодятся интерфейсы с контейнером IoC, таким как StructureMap, в котором я могу объявить использование класса через интерфейс в конструкторе моего класса.

public class Widget(IWidgetRepository repository, IWidgetService service) : IWidget
{
    //do something here using my repository and service
}

А потом, когда я создаю экземпляр Widget с помощью StructureMap, например,

IWidget widget = ObjectFactory.GetInstance<IWidget>();

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

Все из простого определения интерфейсов и их умного использования!

7 голосов
/ 24 июня 2009

Базовым корпусом является корпус "IWriter".

Предположим, вы создаете класс, который может писать на консоль, и в нем есть все виды полезных функций, таких как write () и peek ().

Затем вы хотели бы написать класс, который можно записывать на принтер, поэтому вместо того, чтобы заново изобретать новый класс, вы используете интерфейс IWriter.

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

6 голосов
/ 24 июня 2009

Один простой ответ: Используйте интерфейсы для программирования против контракта , а не реализации .

Как это могло бы помочь? Начало использования интерфейсов (надеюсь) поможет вам привыкнуть к объединению классов более свободно. Когда вы кодируете свои собственные конкретные классы, легко начать совмещать структуры данных без строгого разделения задач. Вы получаете классы, которые «знают» все о других классах, и все может запутаться. Ограничивая себя интерфейсом, вы только гарантируете, что он выполняет контракт интерфейса. Он вводит иногда полезное трение против плотного сцепления.

1 голос
/ 24 июня 2009

IOC и инъекция зависимостей уже упоминались выше, и я призываю вас взглянуть на них.

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

Допустим, у меня есть класс Foo с функциями x и y и свойством z, и я строю свой код вокруг него.

Если я найду лучший способ сделать Foo, или другой тип Foo требует реализации, я, конечно, могу расширить базовый класс Foo до FooA, FooB, MyFoo и т. Д., Однако для этого потребуется, чтобы все Foo имели одинаковый основная функциональность или, действительно, любые будущие создатели Foo имеют доступ к базовому классу Foo и понимают его внутреннюю работу. В C # это означало бы, что будущие Foos не смогут наследовать ни от чего другого, кроме Foo, поскольку C # не поддерживает множественное наследование.

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

Использование интерфейса IFoo просто устанавливает «контракт», который требуется классу для работы в моей среде Foo, и мне все равно, от каких будущих классов Foo могут наследоваться или как они выглядят внутренне, если у них есть fn x F y и z. Это делает структуру намного более гибкой и открытой для будущих дополнений.

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

1 голос
/ 24 июня 2009

Пример. Рассмотрим MDI-приложение, которое показывает отчеты, в основном это 2 разных типа отчетов. Диаграмма и сетка. Мне нужно сохранить эти отчеты в формате PDF и отправить их кому-нибудь по почте. Обработчик событий для меню, которое пользователь щелкает, чтобы сохранить отчет в PDF, может сделать это:

void ExportPDF_Clicked(...) {
   if(currentDocument is ChartReport) {
      ChartReport r = currentDocument as ChartReport;
      r.SavePDF();
   } else if(currentDocument is GridReport) {
     GridReport r = currentDocument as GridReport;
      r.SavePDF();
   }
}

Я лучше сделаю так, чтобы мои ChartReport и GridReport реализовали этот интерфейс:

public interface Report {
  void MailTo();
  void SavePDF();
}

Теперь я могу сделать:

void ExportPDF_Clicked(...) {
   Report r = currentDocument as Report;
   r.SavePDF();
}

Аналогично для другого кода, который должен выполнить ту же операцию (сохранить его в файл, увеличить, распечатать и т. Д.) Для различных типов отчетов. Приведенный выше код все равно будет работать нормально, когда я добавлю PivotTableReport, также стимулирующий Rpoert на следующей неделе.

0 голосов
/ 24 июня 2009

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

Вы можете сделать так, чтобы при загрузке плагина у него был метод Init, который принимает класс, реализующий интерфейс IServices.

public interface IServices
{
    DataManager Data { get; set; }
    LogManager Log { get; set; }
    SomeOtherManager SomeOther { get; set; }
}

public class MrPlugin
{
    public void Init(IServices services)
    {
        // Do stuff with services
    }
}

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

0 голосов
/ 24 июня 2009

Простой ответ, основанный на первых принципах:

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

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

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

0 голосов
/ 24 июня 2009

Пара вещей, когда вы наследуете от интерфейса, это заставляет вас реализовать все методы, определенные в интерфейсе. С другой стороны, это также хороший способ ввести множественное наследование, которое не поддерживается для обычных классов. http://msdn.microsoft.com/en-us/library/ms173156.aspx

0 голосов
/ 24 июня 2009

Вот книга , в которой говорится все об интерфейсах. Это продвигает идею, что интерфейсы принадлежат клиенту , то есть вызывающей стороне. Это хорошее понятие. Если вам нужна только вещь, которую вы вызываете для реализации - скажем - count () и get (), то вы можете определить такой интерфейс и позволить классам реализовать эти функции. Некоторые классы будут иметь много других функций, но вас интересуют только эти два - поэтому вам нужно меньше знать о классах, с которыми вы работаете. Пока они удовлетворяют условиям контракта, вы можете использовать их.

0 голосов
/ 24 июня 2009

хорошая статья.

Интерфейс - это контракт, который гарантирует клиенту поведение класса или структуры.

http://www.codeguru.com/csharp/csharp/cs_syntax/interfaces/article.php/c7563

Это может быть самый простой способ объяснить, с чем я столкнулся:

"Ответ заключается в том, что они предоставляют довольно безопасные для типов средства построения подпрограмм, которые принимают объекты, когда вы не знаете, какой тип объекта будет передан раньше времени. Единственное, что вы знаете об объектах будет передано вашей программе, если у них есть определенные члены, которые должны присутствовать для вашей программы, чтобы иметь возможность работать с этим объектом. Лучший пример необходимости интерфейсов - это командная среда. Интерфейсы помогают определить, как разные компоненты взаимодействуют друг с другом. Используя интерфейс, вы исключаете возможность того, что разработчик будет неправильно интерпретировать, какие члены они должны добавить к типу, или как они будут вызывать другой тип, который определяет интерфейс. Без интерфейса ошибки проникают в систему и не появляются до времени выполнения, когда их трудно найти. При использовании интерфейсов ошибки в определении типа сразу же обнаруживаются во время компиляции, где стоимость намного меньше. "

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