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

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

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

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


person Shaw    schedule 04.12.2008    source источник


Ответы (10)


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

person namin    schedule 04.12.2008

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

person Brian Genisio    schedule 04.12.2008
comment
Это основная причина, по которой я избегал разработки через тестирование. На первый взгляд, TDD — отличная идея, но я думаю, что цена слишком высока, поскольку она требует чрезмерной архитектуры большинства классов приложений. - person benjismith; 06.12.2008
comment
Вот тут я не согласен. Мое тестовое покрытие настолько велико, а мои классы настолько хорошо развязаны, что я вынужден заранее думать о хорошем дизайне очень реальным образом. Он создает более модульную, расширяемую архитектуру, за которой достаточно тестов, чтобы оставаться надежной. Я редко пишу ошибки больше. - person Brian Genisio; 06.12.2008

Вот простой пример, который открыл мне глаза на интерфейсы.

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. Список изменений.

person terjetyl    schedule 04.12.2008
comment
Я знаю о преимуществах встроенных типов и всегда стараюсь их использовать. Но я не уверен в пользовательских типах - в частности, мой вопрос был об этом. Спасибо за ваш ответ и код :) - person Shaw; 04.12.2008
comment
Принцип использования интерфейсов так же актуален для вас, как и для разработчиков библиотек .net, поэтому, если вы понимаете, почему они их использовали, вы поймете, почему и где вы должны их использовать. Конечно, фреймворк гораздо больше выиграет от интерфейсов. - person terjetyl; 04.12.2008

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

person rich    schedule 04.12.2008
comment
Точно. Я могу установить часть лесов, чтобы избежать ненужной работы позже. Но будет ли это называться плохой практикой - из-за чрезмерного использования или плохого применения хорошей практики? - person Shaw; 04.12.2008
comment
Я думаю, что это суждение, основанное на том, что вы реализуете. Один из примеров, когда я бы посоветовал всегда программировать интерфейсы, — это создание уровня DAO, поскольку интерфейсы позволяют использовать разные источники данных и создавать фиктивные данные для модульного тестирования. - person rich; 04.12.2008

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

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

person Tigraine    schedule 04.12.2008
comment
Очень верно. Я часто слышал утверждение: никогда не используйте интерфейс на чем-то с одной реализацией. Что ж, когда вы выполняете модульное тестирование, вы создаете вторую реализацию классов-ответчиков. С хорошим набором модульных тестов у вас ВСЕГДА будет более одной реализации. - person Brian Genisio; 04.12.2008

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

person Trap    schedule 04.12.2008

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

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

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

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

person Vilx-    schedule 04.12.2008

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

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

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

person Mike Hall    schedule 04.12.2008

Просмотрите книгу Head-First Design Patterns... вы увидите веские аргументы в пользу использования интерфейсов, которые не имеют ничего общего с TDD или полиморфизмом.

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

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

public class Employee {
  private IExpenseCalculator expenses;

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

В этом примере предполагается, что «расходы» представлены как свойство и что IExpenseCalculator имеет метод для CalcExpenses().

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

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

person snogfish    schedule 05.12.2008

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

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

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

person Daniel Earwicker    schedule 04.12.2008