Что значит «программировать на интерфейс»? - PullRequest
768 голосов
/ 21 декабря 2008

Я видел это упомянутое несколько раз, и мне не ясно, что это значит. Когда и зачем ты это делаешь?

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

Это просто так, если бы вы делали:

IInterface classRef = new ObjectWhatever()

Вы могли бы использовать любой класс, который реализует IInterface? Когда вам нужно это сделать? Единственное, о чем я могу думать, это если у вас есть метод, и вы не уверены, какой объект будет передан, за исключением того, что он реализует IInterface. Я не могу думать, как часто вам нужно будет это делать.

Кроме того, как вы могли бы написать метод, который принимает объект, который реализует интерфейс? Это возможно?

Ответы [ 32 ]

8 голосов
/ 21 декабря 2008

Если вы программируете на Java, хорошим примером является JDBC. JDBC определяет набор интерфейсов, но ничего не говорит о реализации. Ваши приложения могут быть написаны для этого набора интерфейсов. Теоретически, вы выбираете драйвер JDBC, и ваше приложение будет работать. Если вы обнаружите, что есть более быстрый или «лучший» или более дешевый драйвер JDBC или по какой-либо причине, вы можете теоретически заново настроить файл свойств, и без необходимости вносить изменения в ваше приложение, ваше приложение все равно будет работать. *

7 голосов
/ 01 января 2016

Я опоздал на этот вопрос, но хочу упомянуть, что строка «Программа для интерфейса, а не для реализации» хорошо обсуждалась в книге «Шаблоны проектирования GoF (Gang of Four)».

указано, на с. 18:

Программа для интерфейса, а не реализация

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

и выше, это началось с:

Существует два преимущества управления объектами исключительно в терминах интерфейса, определенного абстрактными классами:

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

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

6 голосов
/ 15 апреля 2013

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

  1. внутренний массив
  2. Связанный список
  3. другая реализация

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

Вы можете использовать любой тип внутренней реализации, скажем, реализацию array. Но предположим, что вы хотите изменить реализацию по какой-то причине, например, из-за ошибки или производительности. Тогда вам просто нужно изменить объявление List<String> ls = new ArrayList<String>() на List<String> ls = new LinkedList<String>().

Нет, где-нибудь еще в коде, вам придется что-то менять; Потому что все остальное было построено на определении List.

6 голосов
/ 09 января 2009

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

4 голосов
/ 21 декабря 2008

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

4 голосов
/ 09 января 2009

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

3 голосов
/ 03 июля 2017

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

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

  1. мешает нам делать неподобающие контексту вещи, а
  2. позволяет нам безопасно изменить реализацию в будущем.

Например, рассмотрим класс Person, который реализует интерфейс Friend и Employee.

class Person implements AbstractEmployee, AbstractFriend {

}

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

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

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

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

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

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

  1. Функция party использует только подмножество Friend из Person.
  2. Функция workplace использует только подмножество Employee из Person.
  3. Класс Dog реализует интерфейсы Friend и Employee.

С другой стороны, если бы party или workplace были запрограммированы против Person, существует риск того, что оба кода будут иметь Person. Изменение с Person на Dog потребовало бы от нас пролистать код для уничтожения любого Person -связанного кода, который Dog не поддерживает.

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

3 голосов
/ 21 декабря 2008

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

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

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

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

3 голосов
/ 19 декабря 2012

C ++ объяснение.

Думайте об интерфейсе как о ваших открытых методах классов.

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

Любой класс алгоритма, который определяет функции / интерфейс, к которым Vector обращается, будет удовлетворять «контракту» (как кто-то объяснил в первоначальном ответе). Алгоритмы даже не должны быть одного и того же базового класса; единственное требование - чтобы в вашем алгоритме были определены функции / методы, от которых зависит Вектор (интерфейс).

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

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

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

Что касается «упущения» в отношении того, как все происходит; большой (по крайней мере, в C ++), поскольку именно так работает большая часть стандартной библиотеки ШАБЛОНОВ.

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

Это огромная тема и один из основополагающих принципов шаблонов проектирования.

3 голосов
/ 22 декабря 2008

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

Например, у меня может быть интерфейс движения. Метод, который заставляет что-то «двигаться», и любой объект (Person, Car, Cat), который реализует интерфейс движения, может быть передан и задан для перемещения. Без метода каждый знает тип класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...