Программирование на интерфейс. Как решить, где это необходимо?

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

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

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

Ответ 1

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

Ответ 2

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

Ответ 3

Простой пример, который открыл мои глаза для интерфейсов, - это

class SimpleClass
{
  public int A { get; set; }
  public int B { get; set; }
  public int C { get; set; }
}

List<SimpleClass> Calc(IEnumerable<SimpleClass> list)
{
  foreach(SimpleClass item in list)
  {
     item.C = item.A * item.C:
  }
  return list.ToList();
}

Обратите внимание на входной параметр IEnumerable. Используя это, я могу пройти в любой коллекции, которая реализует IEnumerable как параметр in. List<SimpleClass>, SimpleClass[], Collection<SimpleClass>.

Интерфейс IEnumerable гарантирует, что я могу использовать цикл foreach, и таким образом я сделал свою функцию более универсальной, и будет меньше шансов, что мне придется изменить этот код, так как вероятность того, что изменения IEnumerable меньше, чем f.ex. Перечислите изменения.

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

Обычно я так думаю - есть только две вещи, которые отделяют интерфейс от реализации:

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

Ответ 10

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

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

Если ваш объект Employee имеет ссылку на интерфейс IExpenseCalculator, вы можете назначить ему калькулятор менеджера или калькулятор сотрудника во время выполнения. Вызов Employee.GetExpenses() даст вам иначе рассчитанный результат для менеджера, чем для обычного сотрудника. Внутренне код будет выглядеть так:

public class Employee {
  private IExpenseCalculator expenses;

  public ExpenseSheet GetExpenses() {
    return expenses.CalcExpenses();
  }
}

В этом примере предполагается, что "затраты" отображаются как свойство и что IExpenseCalculator имеет метод CalcExpenses().

Интерфейсы также тесно связаны с концепцией объектных заводов... считают слой базы данных. Когда вы настроили свой уровень данных как factory, вы можете создавать объекты для подключения к Sql Server, Oracle или MySql динамически во время выполнения на основе параметров конфигурации. Но клиенту нужен конкретный дескриптор объекта уровня базы данных... введите "Интерфейсы".

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