Что значит «программировать на интерфейс»?

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

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

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

IInterface classRef = new ObjectWhatever()

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

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

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

Ande Turner 21.12.2008 07:21

@ Анде Тернер: плохой совет. 1). «ваша программа должна быть оптимальной» - не повод отказываться от интерфейсов! Затем вы говорите: «Распространите свой код, запрограммированный на интерфейсы, хотя ...», таким образом, вы сообщаете, что с учетом требования (1) вы затем выпустите неоптимальный код?!?

Mitch Wheat 21.12.2008 07:26

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

jyoungdev 27.04.2012 20:34

@ apollodude217, что на самом деле лучший ответ на всей странице. По крайней мере, по вопросу в заголовке, поскольку здесь есть как минимум 3 совершенно разных вопроса ...

Andrew Spencer 02.05.2012 12:36

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

Jonathan Allen 19.07.2012 23:37

В основном это делается с помощью COM. Где вы обрабатываете объект, не зная класса, а только то, какой интерфейс он поддерживает.

Jeroen van Langen 15.08.2013 16:34

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

Fattie 08.12.2016 17:30

Классический List lst = new ArrayList(); просто означает, что вы ограничиваете, какие функции для созданного объекта вам доступны, что совершенно глупо. Эту странную конструкцию продолжают распространять учителя и книги Java без всякой мыслимой причины.

Nyerguds 06.01.2017 17:26

@Damien, не объединяйте этот вопрос с дополнительными вопросами. ТАК - это 1 вопрос на тематический сайт. Лучше задавать отдельные вопросы и ссылаться на этот, если он актуален.

Disillusioned 08.01.2017 08:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
839
9
185 936
32
Перейти к ответу Данный вопрос помечен как решенный

Ответы 32

Вы должны изучить Inversion of Control:

В таком сценарии вы бы не написали это:

IInterface classRef = new ObjectWhatever();

Вы бы написали что-то вроде этого:

IInterface classRef = container.Resolve<IInterface>();

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

Если мы оставим IoC вне таблицы, вы можете написать код, который знает, что он может разговаривать с объектом что делает что-то конкретное, но не знает, какой тип объекта или как он это делает.

Это пригодится при передаче параметров.

Что касается вашего вопроса в скобках «Кроме того, как вы могли бы написать метод, который принимает объект, реализующий интерфейс? Возможно ли это?», В C# вы просто использовали бы тип интерфейса для типа параметра, например:

public void DoSomethingToAnObject(IInterface whatever) { ... }

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

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

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

Приведу конкретный пример.

У нас есть специально разработанная система перевода для оконных форм. Эта система просматривает элементы управления в форме и переводит текст в каждом из них. Система знает, как обрабатывать базовые элементы управления, такие как свойство-type-of-control-that-has-a-Text, и аналогичные базовые вещи, но для чего-то базового ей не хватает.

Теперь, поскольку элементы управления наследуются от предопределенных классов, которые мы не контролируем, мы могли бы сделать одно из трех:

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

Итак, мы сделали № 3. Все наши элементы управления реализуют ILocalizable, который представляет собой интерфейс, который дает нам один метод - возможность переводить «себя» в контейнер текста / правил перевода. Таким образом, форме не нужно знать, какой тип элемента управления она обнаружила, только то, что она реализует конкретный интерфейс и знает, что существует метод, который она может вызвать для локализации элемента управления.

Зачем упоминать IoC в самом начале, так как это только добавит путаницы.

Kevin Le - Khnle 21.12.2008 04:13

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

terjetyl 21.12.2008 05:36

Программирование интерфейса говорит: «Мне нужна эта функциональность, и мне все равно, откуда она взялась».

Рассмотрим (в Java) интерфейс List в сравнении с конкретными классами ArrayList и LinkedList. Если все, что меня волнует, это то, что у меня есть структура данных, содержащая несколько элементов данных, к которым я должен получить доступ через итерацию, я бы выбрал List (а это в 99% случаев). Если я знаю, что мне нужна постоянная вставка / удаление с любого конца списка, я мог бы выбрать конкретную реализацию LinkedList (или, что более вероятно, использовать интерфейс Очередь). Если я знаю, что мне нужен произвольный доступ по индексу, я бы выбрал конкретный класс ArrayList.

полностью согласен, то есть независимость между тем, что делается, и тем, как это делается. Разделив систему по независимым компонентам, вы получите простую систему, которую можно использовать повторно (см. Просто стало легко парня, создавшего Clojure)

beluchin 29.12.2013 11:56

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

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

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

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

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

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

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

Обновлено: Ниже приводится ссылка на статью, в которой Эрих Гамма обсуждает свою цитату: «Программа для интерфейса, а не реализация».

http://www.artima.com/lejava/articles/designprinciples.html

Пожалуйста, прочтите еще раз это интервью: Gamma, конечно, говорила о концепции интерфейса OO, а не о JAVA или специальном классе C# (ISomething). Проблема в том, что большинство людей, хотя он говорил о ключевом слове, теперь у нас много ненужных интерфейсов (ISomething).

Sylvain Rodrigue 13.03.2015 15:17

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

Ad Infinitum 21.04.2017 15:07

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

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

Ken Liu 21.12.2008 07:57

JDBC настолько плох, что его нужно заменить. Найдите другой пример.

Joshua 05.01.2011 00:43

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

Erwin Smout 12.03.2019 15:49

Конкретный пример, который я привел студентам, заключается в том, что они должны писать

List myList = new ArrayList(); // programming to the List interface

вместо

ArrayList myList = new ArrayList(); // this is bad

Они выглядят точно так же в короткой программе, но если вы продолжите использовать myList 100 раз в своей программе, вы заметите разницу. Первое объявление гарантирует, что вы вызываете на myList только те методы, которые определены интерфейсом List (то есть никаких методов, специфичных для ArrayList). Если вы запрограммировали интерфейс таким образом, позже вы можете решить, что вам действительно нужно

List myList = new TreeList();

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

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

public ArrayList doSomething(HashMap map);

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

public List doSomething(Map map);

Теперь не имеет значения, какой тип List вы возвращаете или какой Map передается в качестве параметра. Изменения, которые вы вносите внутри метода doSomething, не заставят вас изменить вызывающий код.

Комментарии не подлежат расширенному обсуждению; этот разговор был переехал в чат.

user3956566 12.01.2019 21:51

У меня вопрос о причине, о которой вы упомянули: «Первое объявление гарантирует, что вы вызываете только те методы в myList, которые определены интерфейсом List (то есть никаких специальных методов ArrayList). Если вы запрограммировали интерфейс таким образом, позже вы можете решить, что вам действительно нужен List myList = new TreeList (); и вам нужно изменить свой код только в одном месте ». Может быть, я неправильно понял, мне интересно, почему вам нужно изменить ArrayList на TreeList, если вы хотите «гарантировать, что вы вызываете только методы в myList»?

user3014901 23.10.2019 22:25

@ user3014901 Существует множество причин, по которым вы можете захотеть изменить тип используемого списка. Например, производительность поиска может быть выше. Дело в том, что если вы программируете в интерфейсе List, это упрощает изменение кода на другую реализацию позже.

Bill the Lizard 24.10.2019 16:56

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

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

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

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

Программирование через интерфейсы - это круто, оно способствует слабой связи. Как упоминалось в @lassevk, Inversion of Control отлично подходит для этого.

Кроме того, изучите принципы SOLID.. вот серия видео

Он проходит через жестко запрограммированный (пример с сильной связью), затем просматривает интерфейсы, наконец, переходит к инструменту IoC / DI (NInject).

Ответ принят как подходящий

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

Когда я впервые начал знакомиться с интерфейсами, меня тоже смутила их актуальность. Я не понимал, зачем они вам нужны. Если мы используем такой язык, как Java или C#, у нас уже есть наследование, и я рассматривал интерфейсы как форму наследования слабее и подумал: «Зачем беспокоиться?» В каком-то смысле я был прав, вы можете думать об интерфейсах как о некой слабой форме наследования, но, помимо этого, я наконец понял их использование в качестве языковой конструкции, думая о них как о средстве классификации общих черт или поведения, которые проявляются потенциально много не связанных классов объектов.

Например, скажем, у вас есть игра для SIM-карты и у вас есть следующие классы:

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

Ясно, что эти два объекта не имеют ничего общего с точки зрения прямого наследования. Но можно сказать, что они оба раздражают.

Допустим, в нашей игре должен быть какой-то случайный вещь, который раздражает игрока, когда он ужинает. Это может быть HouseFly, Telemarketer или оба, но как сделать так, чтобы и то, и другое с помощью одной функции? И как вы попросите разные типы объектов одинаково «делать то, что их раздражает»?

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

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

Теперь у нас есть два класса, каждый из которых может по-своему раздражать. И им не обязательно быть производными от одного и того же базового класса и обладать общими присущими характеристиками - им просто нужно соответствовать контракту IPest - этот контракт прост. Вам просто нужно BeAnnoying. В связи с этим можно смоделировать следующее:

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

Здесь у нас есть столовая, которая принимает множество посетителей и множество вредителей - обратите внимание на использование интерфейса. Это означает, что в нашем маленьком мире членом массива pests может быть объект Telemarketer или объект HouseFly.

Метод ServeDinner вызывается, когда подается ужин, и наши люди в столовой должны есть. В нашей маленькой игре именно тогда наши вредители делают свою работу - каждый вредитель получает команду раздражать через интерфейс IPest. Таким образом, мы можем легко заставить и Telemarketers, и HouseFlys раздражать по-своему - нас волнует только то, что в объекте DiningRoom есть что-то, что является вредным организмом, нам все равно, что это такое, и они могут не имеют ничего общего с другими.

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

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

supercat 20.07.2012 18:39

... часто быстрее вызывать метод-заглушку без нужды, чем определять, следует ли вызывать метод. Это одна из причин, по которой IEnumerator<T> наследует IDisposable. В подавляющем большинстве случаев для потребителя счетчика будет дешевле безоговорочно вызвать Dispose на нем, чем для потребителя проверить, необходим ли вызов Dispose. Жаль, что ни .net, ни Java не позволяют указывать реализации по умолчанию для методов интерфейса, поскольку во многих ситуациях интерфейсы могут выиграть от «необязательных» методов, где были бы ...

supercat 20.07.2012 18:48

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

supercat 20.07.2012 19:02

Почему в классе HouseFly все еще есть абстрактные методы?

BAD_SEED 10.02.2014 15:11

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

Peter Meyer 18.02.2014 23:06

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

nckbrz 09.03.2014 23:15

Интересно, что вы не указываете, что, поскольку объекты в IPest[] являются ссылками IPest, вы можете вызвать BeAnnoying(), потому что у них есть этот метод, в то время как вы не можете вызывать другие методы без приведения. Однако для каждого объекта будет вызываться индивидуальный метод BeAnnoying().

D. Ben Knoble 26.05.2015 04:16

Означает ли это, что при выполнении кода в цикле foreach будут вызываться оба метода Be.Annoying()?

user3293145 16.07.2015 17:37

Будет вызван только метод BeAnnoying() класса, который «реализует» или «реализует» интерфейс.

Peter Meyer 28.07.2015 22:47

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

Carlos H Romano 09.08.2015 18:37

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

nawfal 10.11.2015 21:05

хотите больше о «Программе, а не реализации» - как насчет этой части? FlyAroundYourHead() слишком специфичен и подходит для реализованного элемента? То же самое с .bark() и .meow(), которые являются программой для реализации - потому что они слишком сильно привязаны к Dog и Cat. При программировании интерфейса он должен быть .giveSound(), то есть достаточно общим, чтобы не зависеть от конкретной реализации (или определенного подкласса).

nonopolarity 01.01.2016 17:19

Другой дизайн для ситуации: «События» здесь раздражают, и у каждого человека должны быть свои раздражающие события, которые, когда происходят во время обеда, сводят его с ума.

Lost Koder 18.11.2016 08:16

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

Shaun Luttin 03.07.2017 21:31

Конечно, это отличный способ объяснить, почему интерфейсы полезны, однако на самом деле это не ответ на главный вопрос (этот ответ stackoverflow.com/a/383982/9868445 в этом отношении намного лучше)

aderchox 15.09.2018 12:07

Плюс один за отношения между телемаркетологом и комнатной мухой.

MC Emperor 22.09.2018 22:15
a means of classifying common traits or behaviors that were exhibited by potentially many non-related classes of objects, это совершенно точно! Иногда не очевидно, что два класса (или более) имеют общую связь. Хороший ответ :)
James 02.06.2019 20:20

Это единственный лучший ответ, который я когда-либо видел при переполнении стека.

Adam Hughes 12.07.2020 00:09

Вы могли бы добавить, что этот дизайн достаточно слабо связан между обеденной комнатой и различными вредителями, что вы можете ввести новых вредителей (подумайте о class Covid19 implements IPest {...};!), которые даже не существовать, когда был написан класс DiningRoom! Вам не только не нужно менять Diningroom, вам даже не нужно перекомпилировать! Пригодится для тестов и больших проектов.

Peter - Reinstate Monica 26.03.2021 10:58

В Java все эти конкретные классы реализуют интерфейс CharSequence:

CharBuffer, String, StringBuffer, StringBuilder

Эти конкретные классы не имеют общего родительского класса, кроме Object, поэтому нет ничего, что их связывает, кроме того факта, что каждый из них имеет какое-то отношение к массивам символов, представляющих их или манипулирующих ими. Например, символы String не могут быть изменены после создания экземпляра объекта String, тогда как символы StringBuffer или StringBuilder можно редактировать.

Тем не менее, каждый из этих классов может соответствующим образом реализовать методы интерфейса CharSequence:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

В некоторых случаях классы библиотеки классов Java, которые раньше принимали String, были изменены, чтобы теперь принимать интерфейс CharSequence. Поэтому, если у вас есть экземпляр StringBuilder, вместо извлечения объекта String (что означает создание экземпляра нового объекта) он может вместо этого просто передать сам StringBuilder, поскольку он реализует интерфейс CharSequence.

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

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer

Жаль, что интерфейсы вроде CharSequence анемичны. Я бы хотел, чтобы Java и .NET позволяли интерфейсам иметь реализацию по умолчанию, чтобы люди не сокращали интерфейсы исключительно с целью минимизации шаблонного кода. Учитывая любую законную реализацию CharSequence, можно было бы эмулировать большинство функций String, используя только четыре вышеупомянутых метода, но многие реализации могли бы выполнять эти функции гораздо более эффективно другими способами. К сожалению, даже если конкретная реализация CharSequence содержит все в одном char[] и может выполнять многие ...

supercat 18.10.2014 20:58

... такие операции, как indexOf, быстро, нет никакого способа, чтобы вызывающий, не знакомый с конкретной реализацией CharSequence, мог попросить его сделать это, вместо того, чтобы использовать charAt для проверки каждого отдельного символа.

supercat 18.10.2014 21:00

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

// if I want to add search capabilities to my application and support multiple search
// engines such as Google, Yahoo, Live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

тогда я мог бы создать GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider и т. д.

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

затем создайте JpegImageLoader, GifImageLoader, PngImageLoader и т. д.

Большинство надстроек и плагинов работают без интерфейсов.

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

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

тогда я мог бы создать XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository и т. д. Для своих веб-приложений я часто создаю XML-репозитории на раннем этапе, чтобы я мог что-то запустить и запустить до того, как база данных SQL будет готова. Когда база данных готова, я пишу SQLRepository для замены версии XML. Остальная часть моего кода осталась неизменной, так как она работает исключительно через интерфейсы.

Методы могут принимать такие интерфейсы, как:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

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

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

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

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

Если я пишу новый класс Swimmer, чтобы добавить функциональность swim(), и мне нужно использовать объект класса, скажем, Dog, и этот класс Dog реализует интерфейс Animal, который объявляет swim().

На вершине иерархии (Animal) она очень абстрактна, а внизу (Dog) - очень конкретна. Я думаю о «программировании для интерфейсов» так, что, когда я пишу класс Swimmer, я хочу написать свой код для интерфейса, который находится на более высоком уровне иерархии, который в данном случае является объектом Animal. Интерфейс свободен от деталей реализации и, таким образом, делает ваш код слабосвязанным.

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

Программирование интерфейса не имеет абсолютно ничего общего с абстрактными интерфейсами, как мы видим в Java или .NET. Это даже не концепция ООП.

Это означает, что не стоит возиться с внутренним устройством объекта или структуры данных. Используйте абстрактный программный интерфейс или API для взаимодействия с вашими данными. В Java или C# это означает использование общедоступных свойств и методов вместо доступа к необработанным полям. Для C это означает использование функций вместо необработанных указателей.

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

Лучший ответ. Гамма дает аналогичное объяснение здесь: artima.com/lejava/articles/designprinciples.html (см. Стр. 2). Он ссылается на концепцию объектно-ориентированного программирования, но вы правы: она намного шире.

Sylvain Rodrigue 13.03.2015 15:23

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

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

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

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

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

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

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

Что касается «упущения» того, как все работает; большое время (по крайней мере, в C++), поскольку именно так работает большая часть инфраструктуры стандартной библиотеки TEMPLATE.

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

Это огромная тема и один из краеугольных принципов паттернов дизайна.

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

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

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

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

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

Q: - ... "Could you use any class that implements an interface?"
A: - Yes.

Q: - ... "When would you need to do that?"
A: - Each time you need a class(es) that implements interface(s).

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

  • Почему?
  • Поскольку интерфейс имеет только прототипы методов, а не определения (только имена функций, а не их логику)

AnIntf anInst = new Aclass();
// we could do this only if Aclass implements AnIntf.
// anInst will have Aclass reference.


Примечание:Теперь мы могли понять, что произошло бы, если бы Bclass и Cclass реализовали один и тот же Dintf.

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

Что мы имеем: Одинаковые прототипы интерфейса (имена функций в интерфейсе) и вызывают разные реализации.

Библиография:Прототипы - википедия

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

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

Так что лучше оберните его крышкой (в нашей истории это интерфейс), тогда он отлично справится со своей работой.

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

Создайте тип interface, а не фактический тип, а реализуйте его с фактическим типом.

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

Приведу вам пример.

у вас есть интерфейс AirPlane, как показано ниже.

interface Airplane{
    parkPlane();
    servicePlane();
}

Предположим, у вас есть методы в вашем классе контроллеров самолетов, например

parkPlane(Airplane plane)

а также

servicePlane(Airplane plane)

реализовано в вашей программе. Это не ПЕРЕРЫВ ваш код. Я имею в виду, что его не нужно менять, пока он принимает аргументы как AirPlane.

Потому что он принимает любой самолет, независимо от его типа, flyer, highflyr, fighter и т. д.

Также в коллекции:

List<Airplane> plane; // Заберут все ваши самолеты.

Следующий пример прояснит ваше понимание.


У вас есть истребитель, который это реализует, так что

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

То же самое для HighFlyer и других классов:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Теперь подумайте, что ваши классы контроллеров используют AirPlane несколько раз,

Предположим, что ваш класс контроллера - ControlPlane, как показано ниже,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

Здесь приходит волшебство, поскольку вы можете создавать сколько угодно новых экземпляров типа AirPlane, не изменяя при этом код класса ControlPlane.

Вы можете добавить экземпляр ...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Вы также можете удалить экземпляры ранее созданных типов.

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

Модульное тестирование

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

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

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

Сочетание интерфейсов и наследования Я обнаружил, что комбинация очень хороша для использования. Приведу очень простой пример.

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

Таким образом, копирование кода не требуется, но при этом можно использовать автомобиль в качестве интерфейса (ICar).

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

//This interface is very flexible and abstract
    addPassenger(Plane seat, Ticket ticket); 

//Boeing is implementation of Plane
    addPassenger(Boeing747 seat, EconomyTicket ticket); 
    addPassenger(Cessna, BusinessClass ticket);


    addPassenger(J15, E87687); 

Начнем с некоторых определений:

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

Типп. Конкретный интерфейс

Простым примером интерфейс, как определено выше, могут быть все методы объекта PDO, такие как query(), commit(), close() и т. д., В целом, а не по отдельности. Эти методы, то есть его интерфейс, определяют полный набор сообщений, запросов, которые могут быть отправлены объекту.

тип, как определено выше, является конкретным интерфейсом. Я буду использовать интерфейс придуманной формы, чтобы продемонстрировать: draw(), getArea(), getPerimeter() и т. д.

Если объект относится к типу «База данных», мы имеем в виду, что он принимает сообщения / запросы интерфейса базы данных, query(), commit() и т. д. Объекты могут быть разных типов. У вас может быть объект базы данных типа shape, если он реализует свой интерфейс, и в этом случае это будет подтип.

Многие объекты могут иметь множество различных интерфейсов / типов и реализовывать этот интерфейс по-разному. Это позволяет нам заменять объекты, позволяя выбирать, какой из них использовать. Также известен как полиморфизм.

Клиент будет знать только об интерфейсе, но не о реализации.

Таким образом, в сущности, программирование интерфейса потребовало бы создания некоторого типа абстрактного класса, такого как Shape, с указанным только интерфейсом, то есть draw(), getCoordinates(), getArea() и т. д. И затем различные конкретные классы реализуют эти интерфейсы, такие как класс Circle, класс Square, Класс треугольника. Следовательно, программа для интерфейса, а не реализация.

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

В нем говорится, на стр. 18:

Program to an interface, not an implementation

Don't declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class. You will find this to be a common theme of the design patterns in this book.

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

There are two benefits to manipulating objects solely in terms of the interface defined by abstract classes:

  1. Clients remain unaware of the specific types of objects they use, as long as the objects adhere to the interface that clients expect.
  2. Clients remain unaware of the classes that implement these objects. Clients only know about the abstract class(es) defining the interface.

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

Код для интерфейса Не Реализация не имеет НИЧЕГО общего ни с Java, ни с ее конструкцией интерфейса.

Эта концепция получила широкое распространение в книгах «Паттерны / Банда четырех», но, скорее всего, существовала задолго до этого. Эта концепция определенно существовала задолго до того, как появилась Java.

Конструкция интерфейса Java была создана, чтобы помочь в реализации этой идеи (среди прочего), и люди стали слишком сосредоточены на конструкции как на центре смысла, а не на первоначальном замысле. Однако по этой причине у нас есть общедоступные и частные методы и атрибуты в Java, C++, C# и т. д.

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

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

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

Первое предложение говорит само за себя. Это должен быть принятый ответ.

madumlao 23.04.2017 20:41

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

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

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

IInterface classRef = new ObjectWhatever()

You could use any class that implements IInterface? When would you need to do that?

Взгляните на этот вопрос SE в качестве хорошего примера.

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

does using an Interface hit performance?

if so how much?

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

how can you avoid it without having to maintain two bits of code?

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

Один хороший вариант использования: реализация шаблона стратегии:

Пример паттерна стратегии в реальном мире

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

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

  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 не поддерживает.

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

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

Casey 18.07.2017 23:41

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

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

Посмотрите на любой дверь в вашем доме, школе, церкви ... любой строительство.

Представьте, что какой-то двери получил опасности справа внизу (так что вам нужно поклониться, чтобы взаимодействовать с дверью, которая открывает или закрывает ее),

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

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

Interfaces упрощает работу других младших и старших разработчиков в колоссальных проектах [1], чтобы все знали, что они делают, с небольшой помощью других, чтобы вы могли работать как можно более гладко (в теории).


[1]: How? By exposing the shape of value. So, you don't need documentation, cause the code itself is self-explanatory (Awesome).


This answer was not meant to be for language-specific instead of concept-driven (After all, humans are creating tools by writing code).

программа к интерфейсу - это термин из книги GOF. Я бы не сказал прямо, что это связано с java-интерфейсом, скорее с реальными интерфейсами. Для достижения чистого разделения уровней вам необходимо создать некоторое разделение между системами, например: Допустим, у вас есть конкретная база данных, которую вы хотите использовать, вы никогда не будете «программировать для базы данных», вместо этого вы бы «запрограммировали для интерфейса хранилища». Точно так же вы никогда не будете «программировать для веб-службы», а скорее будете программировать для «клиентского интерфейса». это так, чтобы вы могли легко поменять местами.

Я считаю, что эти правила мне помогают:

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

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

обратите внимание на два способа их использования. надеюсь это поможет.

Другие вопросы по теме