Действительно ли это - лучшая практика для извлечения интерфейса для каждого класса?

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

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

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

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

Там какая-либо причина состоит в том, чтобы сделать это?

48
задан user3666197 23 January 2016 в 22:03
поделиться

11 ответов

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

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

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

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

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

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


Я оставлю свой старый ответ для контекста.

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

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

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

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

28
ответ дан 26 November 2019 в 18:44
поделиться

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

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

2
ответ дан 26 November 2019 в 18:44
поделиться

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

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

По мере роста и усложнения вашей системы этот принцип приобретает все больший смысл!

1
ответ дан 26 November 2019 в 18:44
поделиться

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

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

4
ответ дан 26 November 2019 в 18:44
поделиться

Нет.

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

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

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 .

42
ответ дан 26 November 2019 в 18:44
поделиться

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

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

1
ответ дан 26 November 2019 в 18:44
поделиться

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

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

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

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

2
ответ дан 26 November 2019 в 18:44
поделиться

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

5
ответ дан 26 November 2019 в 18:44
поделиться

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

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

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

2
ответ дан 26 November 2019 в 18:44
поделиться

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

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

1
ответ дан 26 November 2019 в 18:44
поделиться

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

0
ответ дан 26 November 2019 в 18:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: