Почему классы в настоящее время определяются как интерфейс?

Эти 2-3 последних года, многие проекты, которые я вижу, такие как С# CMS Cyyahoga с открытым исходным кодом, имеют тенденцию определять постоянные и непостоянные классы как Interface. Зачем? Есть ли веская причина? TDD? Дразнящий? Модель дизайна?...

Ответ 1

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

Простой пример:

Скажите, что у вас есть метод, который вычисляет заработную плату emplyoee. В рамках своей подписи он принимает объект, который вычисляет их преимущества, например, экземпляр BenefitCalculator:

calculateSalary(... BenefitCalculator bc, ...)

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

Теперь, если вы используете подпись

calculateSalary(... BenefitCalculator bc, ...)

вы отвратительны, потому что не можете предлагать разные реализации. Если вы используете

calculateSalary (... IBenefitCalculator bc,...)

вы можете просто реализовать все классы интерфейса.

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

Ответ 2

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

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

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

public class TransactionProcessor
{
    public void ProcessDeposit( // Process Deposit );
    public void ProcessWithdraw( // Process Withdraw );
    public void ProcessTransfer( // Process Transfer );
}

И каждый раз, когда кто-то добавляет новый тип транзакции, вы должны изменить свой класс. Или вы могли:

public interface ITransaction { void Process(); }

public class TransactionProcessor
{
    public void ProccessTransaction(ITransaction t) { t.Process(); }
}

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

Это позволяет вам обменивать реализацию интерфейса в зависимости от ваших потребностей. Он также позволяет такие вещи, как Injection Dependency and Mocking Frameworks для модульного тестирования.

В общем, это действительно просто еще один способ сделать ваш код более гибким.

Ответ 3

Интерфейсы имеют то преимущество, что они делают вас независимыми от реализации, что хорошо.

Ответ 4

В последние годы контейнеры IoC становятся довольно популярными у разработчиков. Например, Unity Container из Microsoft Practices. Таким образом, в начале вашего приложения вы можете зарегистрировать конкретные классы, которые реализуют интерфейсы, а затем, например, все классы, которые содержат эти интерфейсы в своих конструкторах, или их свойства, помеченные атрибутом [Dependency], будут заполнены при инстанцировании объектов через Разрешить контейнер Unity. Это очень полезно в приложениях со сложными зависимостями, когда один интерфейс может быть реализован в трех разных классах. И все это не может быть достигнуто без использования интерфейсов.

Ответ 5

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

public class A {
   B b;
}

public class B {
   public int getCount() {
       return 10;
   }
}

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

Вместо этого мы используем интерфейсы:

class A {
   IB b;
}

interface IB {
   int getCount();
}

class B : IB {
   public int getCount() {
       return 10;
   }
}

В этом случае A зависит только от IB. Никакое изменение в B не требует какого-либо рассмотрения A во время компиляции.

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

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

class A {
    IB b = new B();
}

Здесь вводится инъекция зависимостей. Контейнер DI должен построить B и предоставить его A в качестве IB, чтобы A не нуждался в статической зависимости.