Это лучшая практика для извлечения интерфейса для каждого класса? - PullRequest
47 голосов
/ 14 июня 2010

Я видел код, в котором у каждого класса есть интерфейс, который он реализует.

Иногда нет общего интерфейса для них всех.

Они просто есть и используются вместо бетона.объекты.

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

Есть ли причина для этого?

Ответы [ 13 ]

41 голосов
/ 14 июня 2010

номер

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

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

class Coordinate
{
  public Coordinate( int x, int y);
  public int X { get; }
  public int y { get; }
}

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

Тем не менее, класс

class RoutePlanner
{
   // Return a new list of coordinates ordered to be the shortest route that
   // can be taken through all of the passed in coordinates.
   public List<Coordinate> GetShortestRoute( List<Coordinate> waypoints );
}

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

Кроме того, если у вас был третий класс:

class RobotTank
{
   public RobotTank( IRoutePlanner );
   public void DriveRoute( List<Coordinate> points );
}

Предоставляя RoutePlanner интерфейс, вы можете написать тестовый метод для RobotTank, который создает метод с макетом RoutePlanner, который просто возвращает список координат в произвольном порядке. Это позволило бы тестовому методу проверить, что танк правильно перемещается между координатами, и при этом не проверять планировщик маршрута. Это означает, что вы можете написать тест, который просто тестирует одну единицу (танк), без проверки планировщика маршрута.

Вы увидите, что довольно просто ввести реальные координаты в такой тест без необходимости скрывать их за интерфейсом ICoordinate.

27 голосов
/ 14 июня 2010

После повторного рассмотрения этого ответа я решил немного его изменить.

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

  • Поддержка тестов (макеты, заглушки).
  • Абстракция реализации (продолжение на IoC / DI).
  • Вспомогательное оборудованиетакие вещи, как совместная и противоположная поддержка в C #.

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

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

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

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

В настоящее время для немедленного использования это Unit Testing .Интерфейсы легко подделать, заглушки, подделки, назовите это.

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

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

8 голосов
/ 26 января 2016

Позвольте мне процитировать г-на О.О., Мартина Фаулера, чтобы добавить некоторые веские обоснования к наиболее распространенному ответу в этой теме.

Этот отрывок взят из "Шаблоны архитектуры корпоративных приложений" (зачислен в категорию "классика программирования" и \ или "каждый разработчик должен читать").

[Pattern] Отдельный интерфейс

(...)

Когда его использовать

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

(...)

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

Отвечая на ваш вопрос: нет

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

5 голосов
/ 14 июня 2010

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

4 голосов
/ 14 июня 2010

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

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

2 голосов
/ 14 июня 2010

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

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

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

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

2 голосов
/ 14 июня 2010

Я не думаю, что это целесообразно для каждого класса.

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

2 голосов
/ 14 июня 2010

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

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

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

1 голос
/ 14 июня 2010

Интерфейсы хороши, поскольку вы можете имитировать классы при (модульном) тестировании.

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

1 голос
/ 14 июня 2010

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

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

...