Я часто сталкивался с понятием «программирование для интерфейса вместо реализации», и я думаю, что понимаю, что это значит. Но я хочу убедиться, что я понимаю, что это преимущества и возможные реализации.
«Программирование на интерфейсе» означает, что, когда это возможно, следует ссылаться на более абстрактный уровень класса (интерфейс, абстрактный класс или иногда некоторый суперкласс) вместо обращения к конкретной реализации.
Типичным примером в Java является использование:
List myList = new ArrayList();
вместо ArrayList myList = new ArrayList();
.
У меня есть два вопроса по этому поводу:
Я хочу убедиться, что я понимаю основные преимущества этого подхода. Я думаю, что преимущества в основном гибкость. Объявление объекта в качестве более высокоуровневой ссылки, а не конкретной реализации, обеспечивает большую гибкость и поддержку в течение всего цикла разработки и всего кода. Это верно? Является ли гибкость главным преимуществом?
Есть ли еще способы «программирования на интерфейс»? Или «объявление переменной как интерфейса, а не конкретной реализации» - единственная реализация этой концепции?
Я не говорю об интерфейсе конструкции Java . Я говорю о принципе ОО «программирование на интерфейс, а не на реализацию». В этом принципе мировой «интерфейс» относится к любому «супертипу» класса - интерфейсу, абстрактному классу или простому суперклассу, который является более абстрактным и менее конкретным, чем его более конкретные подклассы.
источник
Ответы:
Это не правильно . Или, по крайней мере, это не совсем правильно.
Более важный момент приходит с точки зрения разработки программы. Здесь «программирование в интерфейсе» означает сосредоточение вашего дизайна на том, что делает код, а не на том , как он это делает. Это важное различие, которое подталкивает ваш дизайн к правильности и гибкости.
Основная идея заключается в том, что домены меняются гораздо медленнее, чем программное обеспечение. Скажем, у вас есть программное обеспечение для отслеживания вашего списка покупок. В 80-х это программное обеспечение работало с командной строкой и некоторыми плоскими файлами на дискете. Тогда вы получили пользовательский интерфейс. Тогда вы можете положить список в базе данных. Позже он может быть перемещен в облако или мобильные телефоны или интеграцию с Facebook.
Если бы вы разрабатывали свой код специально для реализации (дискеты и командные строки), вы были бы плохо подготовлены к изменениям. Если вы разработали свой код вокруг интерфейса (манипулируя списком продуктов), то реализация может измениться.
источник
List myList = new ArrayList()
вместоArrayList myList = new ArrayList()
. (Вопрос в следующем комментарии)List
/ArrayList
это совсем не то, о чем я говорю. Это больше похоже на предоставлениеEmployee
объекта, а не набора связанных таблиц, которые вы используете для хранения записи сотрудника. Или предоставляя интерфейс для перебора песен, и не заботясь о том, перемешаны ли эти песни, или на компакт-диске, или в потоковом режиме из Интернета. Это просто последовательность песен.Мое понимание «программирования на интерфейсе» отличается от того, что предлагает вопрос или другие ответы. Это не означает, что мое понимание правильное или что в других ответах нет хороших идей, просто это не то, о чем я думаю, когда слышу этот термин.
Программирование на интерфейсе означает, что когда вам предоставляется какой-то программный интерфейс (будь то библиотека классов, набор функций, сетевой протокол или что-то еще), вы используете только вещи, гарантированные интерфейсом. У вас могут быть знания о базовой реализации (возможно, вы ее написали), но вам никогда не следует использовать эти знания.
Например, скажем, API предоставляет вам какое-то непрозрачное значение, которое является «дескриптором» чего-то внутреннего. Ваши знания могут сказать вам, что этот дескриптор действительно является указателем, и вы можете разыменовать его и получить доступ к некоторому значению, которое может позволить вам легко выполнить некоторую задачу, которую вы хотите выполнить. Но интерфейс не предоставляет вам такую возможность; это ваше знание конкретной реализации, которая делает.
Проблема заключается в том, что он создает сильную связь между вашим кодом и реализацией, что и должен был предотвратить интерфейс. В зависимости от политики это может означать, что реализация больше не может быть изменена, потому что это может нарушить ваш код, или что ваш код очень хрупок и продолжает ломаться при каждом обновлении или изменении базовой реализации.
Большой пример этого - программы, написанные для Windows. WinAPI - это интерфейс, но многие люди использовали приемы, которые работали из-за конкретной реализации, скажем, в Windows 95. Эти приемы, возможно, делали их программы быстрее или позволяли им делать вещи в меньшем количестве кода, чем было бы необходимо в противном случае. Но эти хитрости также означали, что программа зависала в Windows 2000, потому что API там реализован по-другому. Если бы программа была достаточно важной, Microsoft могла бы на самом деле пойти дальше и добавить некоторый взлом к их реализации, чтобы программа продолжала работать, но цена этого - повышенная сложность (со всеми вытекающими проблемами) кода Windows. Это также усложняет жизнь пользователям Wine, потому что они также пытаются реализовать WinAPI, но могут обратиться только к документации, чтобы узнать, как это сделать,
источник
Я могу говорить только о своем личном опыте, так как этому я тоже никогда официально не обучался.
Ваш первый пункт верен. Полученная гибкость происходит из-за невозможности случайного вызова деталей реализации конкретного класса, где они не должны вызываться.
Например, рассмотрим
ILogger
интерфейс, который в настоящее время реализован как конкретныйLogToEmailLogger
класс.LogToEmailLogger
Класс предоставляет всеILogger
методы и свойства, но случается, есть свойство конкретной реализацииsysAdminEmail
.Когда ваш регистратор используется в вашем приложении, он не должен заботиться о потребляющем коде для установки
sysAdminEmail
. Это свойство должно быть установлено во время настройки регистратора и должно быть скрыто от мира.Если вы программируете против конкретной реализации, вы можете случайно установить свойство реализации при использовании регистратора. Теперь код вашего приложения тесно связан с вашим регистратором, и для переключения на другой регистратор потребуется сначала отделить ваш код от исходного.
В этом смысле кодирование интерфейса ослабляет связь .
Относительно вашего второго замечания. Еще одна причина, по которой я видел кодирование интерфейса, заключается в уменьшении сложности кода.
Например, представьте себе , у меня есть игра со следующими интерфейсами
I2DRenderable
,I3DRenderable
,IUpdateable
. Нередко отдельные компоненты игры имеют как 2D, так и 3D визуализируемый контент. Другие компоненты могут быть только 2D и другие только 3D.Если 2D-рендеринг выполняется одним модулем, то имеет смысл сохранить коллекцию
I2DRenderable
s. Не имеет значения, являются ли объекты в его коллекции также,I3DRenderable
илиIUpdateble
как другие модули будут отвечать за обработку этих аспектов объектов.Хранение визуализируемых объектов в виде списка
I2DRenderable
снижает сложность класса рендеринга. Логика 3D-рендеринга и обновления не имеет значения, поэтому эти аспекты дочерних объектов можно и нужно игнорировать.В этом смысле кодирование интерфейса поддерживает сложность на низком уровне, изолируя проблемы .
источник
Здесь возможно использование слова «интерфейс» в двух случаях. Интерфейс, на который вы в основном ссылаетесь в своем вопросе, - это интерфейс Java . Это определенно концепция Java, в более общем смысле это интерфейс языка программирования.
Я бы сказал, что программирование интерфейса - это более широкое понятие. Популярные сейчас API-интерфейсы REST, доступные для многих веб-сайтов, являются еще одним примером более широкой концепции программирования интерфейса на более высоком уровне. Создавая слой между внутренней работой вашего кода и внешним миром (люди в Интернете, другие программы и даже другие части одной и той же программы), вы можете изменить что-либо внутри своего кода, если вы не измените внешний мир ожидает, где это определяется интерфейсом или контрактом, который вы намереваетесь выполнить.
Это тогда дает вам гибкость для рефакторинга вашего внутреннего кода без необходимости рассказывать обо всех других вещах, которые зависят от его интерфейса.
Это также означает, что ваш код должен быть более стабильным. Придерживаясь интерфейса, вы не должны нарушать чужой код. Когда вам действительно нужно изменить интерфейс, вы можете выпустить новую основную версию (1.abc to 2.xyz) API, которая сигнализирует о существенных изменениях в интерфейсе новой версии.
Как отмечает @Doval в комментариях к этому ответу, существует также множество ошибок. Я думаю, что все это сводится к инкапсуляции. Так же, как вы использовали бы его для объектов в объектно-ориентированном дизайне, эта концепция также полезна на более высоком уровне.
источник
Аналогия с реальным миром может помочь:
Сетевой штепсель в интерфейсе.
Да; эта трехконтактная вещь на конце шнура питания от вашего телевизора, радио, пылесоса, стиральной машины и т.д ..
Любое устройство, имеющее основной штепсель (т.е. реализующий интерфейс «имеет сетевой штепсель»), может обрабатываться точно так же; все они могут быть подключены к сетевой розетке и могут получать питание от этой розетки.
То, что делает каждый отдельный прибор , совершенно другое. Вы не будете слишком далеко чистить ковры с помощью телевизора, и большинство людей не смотрят их стиральную машину для развлечения. Но все эти устройства имеют общее свойство быть подключенным к сетевой розетке.
Это то, что вам дают интерфейсы.
Унифицированное поведение, которое может быть реализовано многими различными классами объекта, без необходимости усложнения наследования.
источник
Термин «программирование для интерфейса» открыт для толкования. Интерфейс в разработке программного обеспечения - очень распространенное слово. Вот как я объясняю эту концепцию младшим разработчикам, которых я обучал годами.
В архитектуре программного обеспечения существует множество естественных границ. Общие примеры включают
Важно то, что когда эти естественные границы существуют, они идентифицируются и указывается договор о том, как эта граница ведет себя. Вы проверяете свое программное обеспечение не по тому, ведет ли себя «другая сторона», а по тому, соответствуют ли ваши взаимодействия спецификации.
Последствия этого:
Хотя многое из этого может относиться к классам и интерфейсам, важно понимать, что это также относится к моделям данных, сетевым протоколам и, в более общем смысле, работе с несколькими разработчиками.
источник