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

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

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

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

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

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

Ответ 1

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

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

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

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

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

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


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

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

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

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

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

Ответ 2

Нет.

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

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

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, который создает один с mock RoutePlanner, который просто возвращает список координат в определенном порядке. Это позволит методу проверки проверить, правильно ли перемещается цистерна между координатами, не тестируя также планировщик маршрута. Это означает, что вы можете написать тест, который просто проверяет один блок (резервуар), не тестируя также планировщика маршрута.

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

[Pattern] Разделенный интерфейс

(...)

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

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

(...)

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

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

Ответ 13

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

См. ya