Розуміння "програмування на інтерфейс"


30

Я багато натрапив на термін "програмування на інтерфейс замість реалізації", і я думаю, що я начебто розумію, що це означає. Але я хочу переконатися, що я розумію, що це переваги та можливі реалізації.

"Програмування на інтерфейс" означає, що, коли це можливо, слід посилатися на більш абстрактний рівень класу (інтерфейс, абстрактний клас або іноді якийсь суперклас), а не на конкретні реалізації.

Загальний приклад на Java - це використання:

List myList = new ArrayList();замість ArrayList myList = new ArrayList();.

У мене є два питання щодо цього:

  1. Я хочу переконатися, що я розумію основні переваги такого підходу. Я думаю, що переваги - це переважно гнучкість. Оголошення об’єкта як більш довідкового рівня, а не конкретної реалізації, дозволяє досягти більшої гнучкості та стійкості упродовж усього циклу розробки та в усьому коді. Це правильно? Чи є гнучкість головною перевагою?

  2. Чи існує більше способів "програмування на інтерфейс"? Або "оголошення змінної як інтерфейсу, а не конкретної реалізації" є єдиною реалізацією цієї концепції?

Я не говорю про інтерфейс Java-конструкти . Я говорю про принцип ОО "програмування на інтерфейс, а не реалізація". У цьому принципі світовий "інтерфейс" відноситься до будь-якого "супертипу" класу - інтерфейсу, абстрактного класу або простого суперкласу, який є більш абстрактним і менш конкретним, ніж це більш конкретні підкласи.


можливий дублікат Чому корисні інтерфейси?
гнат


1
Ця відповідь дає вам легкий для розуміння приклад programmers.stackexchange.com/a/314084/61852
Tulains Córdova

Відповіді:


45

"Програмування на інтерфейс" означає, що, коли це можливо, слід посилатися на більш абстрактний рівень класу (інтерфейс, абстрактний клас або іноді якийсь суперклас), а не на конкретні реалізації.

Це неправильно . Або принаймні, це не зовсім правильно.

Більш важливий момент випливає з точки зору дизайну програми. Тут "програмування на інтерфейс" означає зосередження вашого дизайну на тому, що робить код, а не на тому, як він це робить. Це життєва відмінність, яка підштовхує ваш дизайн до правильності та гнучкості.

Основна ідея полягає в тому, що домени змінюються набагато повільніше, ніж це робить програмне забезпечення. Скажіть, у вас є програмне забезпечення для відстеження вашого списку продуктів. У 80-х роках це програмне забезпечення буде працювати проти командного рядка та деяких плоских файлів на дискеті. Тоді ви отримали інтерфейс користувача. Тоді ви, можливо, помістите список у базу даних. Пізніше він, можливо, перейшов до хмари чи мобільних телефонів чи інтеграції у facebook.

Якщо ви створили свій код конкретно навколо реалізації (дискети та командні рядки), ви були б погано підготовлені до змін. Якщо ви створили свій код навколо інтерфейсу (маніпулюючи продуктовим списком), то реалізація може змінюватися.


Дякую за відповідь. Судячи з написаного вами, я думаю, що я розумію, що означає "програмування на інтерфейс" і в чому його переваги. Але у мене є одне питання - Найпоширеніший конкретний приклад для цієї концепції: це: створюючи посилання на об'єкт, зробіть тип посилання інтерфейсу, який цей об’єкт реалізує (або надкласу, який цей об'єкт успадковує), замість того, щоб створювати тип посилання тип об'єкта. (Aka, List myList = new ArrayList()а не ArrayList myList = new ArrayList(). (Питання в наступному коментарі)
Aviv Cohn

Моє запитання: Чи можете ви надати більше прикладів для місць у реальному коді світу, де відбувається принцип "програмування на інтерфейс"? Окрім загального прикладу, який я описав в останньому коментарі?
Авів Кон

6
НІ . List/ ArrayListзовсім не те, про що я говорю. Це скоріше як надання Employeeоб’єкта, а не набору пов'язаних таблиць, які ви використовуєте для зберігання записів працівника. Або надання інтерфейсу для ітерації пісень, а не піклування про те, чи ці пісні перетасовуються, чи на компакт-диску, чи в потоковому режимі з Інтернету. Вони просто послідовність пісень.
Теластин

1
Існування "турбо-кнопки": en.wikipedia.org/wiki/Turbo_button є фактичним прикладом реального світу вашої аналогії дискети.
JimmyJames

1
@AvivCohn Я б запропонував SpringFramework як хороший приклад. Все навесні можна покращити або налаштувати, зробивши власне втілення їхніх інтерфейсів та основних функцій Спрингса, щоб функції продовжували працювати, як очікувалося ... Або у випадку налаштувань, як ви очікували. Програмування на інтерфейс - це також найкраща стратегія проектування інтеграційних рамок. Знову Весна робить свою весну-інтеграцію. На час розробки програмне забезпечення на інтерфейс - це те, що ми робимо з UML. Або це я б робив. Абстрагуйтесь від того, як це робиться, і зосередиться на тому, що робити.
Лаїв

19

Моє розуміння "програмування на інтерфейс" відрізняється від того, що пропонує запитання чи інші відповіді. Що не означає, що моє розуміння є правильним або що речі інших відповідей не є гарними ідеями, просто те, що вони не те, про що я думаю, коли чую цей термін.

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

Наприклад, скажімо, API дає вам якесь непрозоре значення, яке є "ручкою" до чогось внутрішнього. Ваші знання можуть сказати вам, що ця ручка - це справді вказівник, і ви можете знеструмити її та отримати деяке значення, що може допомогти вам легко виконати якесь завдання, яке ви хочете виконати. Але інтерфейс не надає вам такий варіант; це знання вашої конкретної реалізації.

Проблема в цьому полягає в тому, що він створює міцну зв'язок між вашим кодом та реалізацією, саме те, що повинен був запобігти інтерфейс. Залежно від політики це може означати, що реалізацію більше не можна змінювати, оскільки це порушить ваш код, або що ваш код дуже крихкий і постійно ламається при кожному оновленні або зміні базової реалізації.

Великий приклад цього - програми, написані для Windows. WinAPI - це інтерфейс, але багато людей використовували хитрощі, які працювали через особливу реалізацію в, скажімо, Windows 95. Ці трюки, можливо, робили їх програми швидшими або дозволяли їм робити речі за меншим кодом, ніж інакше було б потрібно. Але ці хитрощі також означали, що програма вийде з ладу на Windows 2000, тому що API там реалізовувались по-іншому. Якщо програма була досить важливою, Microsoft може насправді піти наперед і додати деякий хак до їх реалізації, щоб програма продовжувала працювати, але вартість цього - це підвищена складність (з усіма витікаючими з цього проблемами) коду Windows. Це також робить життя винним людям надзвичайно важким, оскільки вони також намагаються реалізувати WinAPI, але для того, як це зробити, вони можуть посилатися лише на документацію,


Це хороший момент, і я багато чого чую в певних контекстах.
Теластин

Я бачу вашу думку. Тож дозвольте мені побачити, чи можу я адаптувати те, що ви говорите, до загального програмування: Скажімо, у мене є клас (клас A), який використовує функціональні можливості абстрактного класу B. Класи C і D успадковують клас B - вони забезпечують конкретну реалізацію для того, що клас, як кажуть, роблять. Якщо клас A використовує безпосередньо клас C або D, він називається "програмування на реалізацію", що не є дуже гнучким рішенням. Але якщо клас A використовує посилання на клас B, яке згодом можна встановити на реалізацію C або на реалізацію D, це робить речі більш гнучкими та рентабельними. Це правильно?
Авів Кон

Якщо це правильно, ніж моє запитання: чи є більш конкретні приклади "програмування на інтерфейс", окрім "загального прикладу" використання посилання на інтерфейс, а не конкретний посилання на клас "?
Авів Кон

2
@AvivCohn Трохи запізнюємось у цій відповіді, але одним із конкретних прикладів є всесвітня павутина. Під час війн у браузері (епоха IE 4) веб-сайти писалися не на те, що сказала будь-яка специфікація, а на химерність певного браузера (Netscape або IE). Це в основному програмування на реалізацію замість інтерфейсу.
Себастьян Редл

9

Я можу говорити лише про свій особистий досвід, тому що це мене ніколи формально не вчили.

Ваш перший пункт правильний. Набута гнучкість пояснюється тим, що ми не можемо випадково викликати деталі реалізації конкретного класу там, де їх не слід використовувати.

Наприклад, розглянемо ILoggerінтерфейс, який в даний час реалізований як конкретний LogToEmailLoggerклас. LogToEmailLoggerКлас надає всі ILoggerметоди і властивості, але трапляється, є властивість конкретної реалізації sysAdminEmail.

Коли ваш реєстратор використовується у вашій програмі, не варто турбуватися про споживчий код для встановлення sysAdminEmail. Цю властивість слід встановити під час налаштування реєстратора та слід приховати від світу.

Якщо ви кодували конкретну реалізацію, ви можете випадково встановити властивість реалізації під час використання реєстратора. Тепер ваш код програми щільно пов'язаний з вашим реєстратором, і для переходу на інший реєстратор потрібно спочатку від’єднати ваш код від початкового.

У цьому сенсі кодування до інтерфейсу послаблює зв'язок .

Стосовно вашого другого пункту: Ще одна причина, яку я бачила при кодуванні інтерфейсу, - це зменшити складність коду.

Наприклад, уявіть собі , у мене є гра з наступними інтерфейсами I2DRenderable, I3DRenderable, IUpdateable. Не рідкість, коли в одних ігрових компонентах є вміст 2D та 3D, який можна передати. Інші компоненти можуть бути лише 2D, а інші лише 3D.

Якщо 2D-рендерінг здійснюється одним модулем, то має сенс підтримувати колекцію I2DRenderables. Не має значення, чи є також об'єкти в його колекції I3DRenderableабо IUpdatebleяк інші модулі будуть відповідати за обробку цих аспектів об'єктів.

Зберігання об'єктів, що передаються, як список, I2DRenderableзабезпечує низьку складність класу візуалізації. Логіка візуалізації та оновлення 3D не викликає жодних проблем, тому ці аспекти об'єктів дітей можна і потрібно ігнорувати.

У цьому сенсі кодування до інтерфейсу утримує низьку складність, виділяючи проблеми .


4

Тут, можливо, є два вживання словоінтерфейсу. Інтерфейс, на який ви головним чином посилаєтесь у своєму питанні, - це інтерфейс Java . Це конкретно концепція Java, загалом це інтерфейс мови програмування.

Я б сказав, що програмування на інтерфейс - це більш широке поняття. На сьогодні популярні API REST, які доступні для багатьох веб-сайтів, є ще одним прикладом більш широкої концепції програмування на інтерфейс на більш високому рівні. Створюючи шар між внутрішньою роботою коду та зовнішнім світом (люди в Інтернеті, інших програмах, навіть інших частинах тієї ж програми), ви можете змінити все, що знаходиться в коді, доки ви не зміните те, що зовнішній світ очікує, де це визначено інтерфейсом чи контрактом, який ви маєте намір виконати.

Тоді це дає вам можливість змінити свій внутрішній код, не розказуючи всі інші речі, які залежать від його інтерфейсу.

Це також означає, що ваш код повинен бути стабільнішим. Дотримуючись інтерфейсу, ви не повинні порушувати код інших людей. Коли вам дійсно доведеться змінити інтерфейс, ви можете випустити нову основну версію (1.abc до 2.xyz) API, яка сигналізує про те, що в інтерфейсі нової версії є зміни.

Як зазначає @Doval в коментарях до цієї відповіді, також існує кількість помилок. Я думаю, це все зводиться до інкапсуляції. Як ви використовували його для об'єктів в об'єктно-орієнтованому дизайні, так і ця концепція корисна на більш високому рівні.


1
Перевагою, яку зазвичай не помічають, є локальність помилок. Скажіть, вам потрібна карта, і ви реалізуєте її за допомогою двійкового дерева. Для того, щоб це працювало, ключі повинні мати деяке впорядкування, і вам потрібно підтримувати інваріант, щоб клавіші, які є "меншими за" ключ поточного вузла, знаходилися в лівому піддереві, а ті, які "більше, ніж", перебувають у праве піддерево. Якщо ви ховаєте реалізацію Map за інтерфейсом, якщо пошук Map не виходить, ви знаєте, що помилка повинна бути в модулі Map. Якщо він виявиться, помилка може бути в будь-якій точці програми. Для мене це головна користь.
Доваль

4

Аналогія реального світу може допомогти:

Мережевий шнур живлення в інтерфейсі.
Так; ця трижильна річ на кінці шнура живлення від телевізора, радіо, пилососа, пральної машини тощо.

Будь-який прилад, який має основний штекер (тобто реалізує інтерфейс "має вилку живлення"), може оброблятись точно так само; всі вони можуть бути підключені до розетки і можуть живити живлення з цієї розетки.

Що кожне окреме пристрій робить зовсім інакше. Ви не збираєтеся дуже далеко чистити килими за допомогою телевізора, і більшість людей не дивляться свою пральну машину для розваги. Але всі ці прилади поділяють загальну поведінку про можливість підключення до розетки.

Ось що ви отримуєте інтерфейси.
Уніфікована поведінка, яка може здійснюватися багатьма різними Класами Об'єкта, не потребуючи ускладнень Успадкування.


1

Термін "програмування на інтерфейс" відкритий для багато тлумачення. Інтерфейс у розробці програмного забезпечення - дуже поширене слово. Ось як я пояснюю цю концепцію молодшим розробникам, яких я навчав за ці роки.

У архітектурі програмного забезпечення існує широкий спектр природних меж. Загальні приклади включають

  • Межева мережа між процесом клієнт і сервер
  • Межа API між додатком та бібліотекою сторонніх розробників
  • Внутрішня межа коду між різними доменами бізнесу в межах програми

Що важливо, це те, що коли ці природні кордони існують, вони визначаються і визначається договір того, як поводиться цей кордон. Ви перевіряєте своє програмне забезпечення не на те, чи поводиться "інша сторона", а на те, чи відповідає ваша взаємодія специфікації.

Наслідки цього:

  • Зовнішні компоненти можна обміняти до тих пір, поки вони реалізують специфікацію
  • Природна точка для одиничних тестів для підтвердження правильності поведінки
  • Інтеграційні тести стали важливими - специфікація була неоднозначною?
  • Як розробник у вас є менший занепокоєний світ при роботі над певним завданням

Хоча велика частина цього може стосуватися класів та інтерфейсів, настільки ж важливо усвідомити, що це також стосується моделей даних, мережевих протоколів і більш загальним способом роботи з декількома розробниками.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.