Чи слід інтерфейси розміщувати в окремому пакеті? [зачинено]


80

Я новачок у команді, яка працює над досить великим проектом з великою кількістю компонентів та залежностей. Для кожного компонента існує interfacesпакет, де розміщуються відкриті інтерфейси для цього компонента. Це хороша практика?

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


4
Чому це позначено [мовно-агностичним]?
finnw

Інша ситуація, коли це може бути кращим, якщо модуль повинен передаватися за допомогою якогось rpc, а не прямого виклику - тоді інтерфейс стане в нагоді для генерації проксі для клієнта ??
redzedi

Відповіді:


101

Розміщення інтерфейсу та реалізації є загальним місцем, і, схоже, не проблема.

Візьмемо для прикладу Java API - більшість класів мають обидва інтерфейси та їх реалізації, включені в один пакет.

Візьмемо для прикладу java.utilпакет:

Він містить інтерфейси , такі як Set, Map, List, в той же час маючи реалізацій , таких як HashSet, HashMapі ArrayList.

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

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

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

  • Префікс назви інтерфейсу додайте до I. Цей підхід застосовується до інтерфейсів у середовищі .NET. Було б досить легко сказати, що IListце інтерфейс для списку.

  • Використовуйте ableсуфікс - . Такий підхід часто розглядається в API Java, наприклад Comparable, Iterableі Serializableдеякі з них.


43
+1, хоча я б не рекомендував конвенцію про іменування I *, якщо ви не перебуваєте у .NETverse, і навіть тоді лише для узгодженості
CurtainDog,

7
Я використав цю конвенцію в коді Java, і я знайшов її корисною .... YMMV
hhafez

5
Я знаю, що це щось старе, але, на мою думку, зараз пропонують це чітко назвати інтерфейс і назвати реалізацію дещо незрозумілішим. наприклад Interface = Store Implementation = StoreImpl Головним чином, щоб підкреслити, що інтерфейс повинен використовуватися над реалізацією.
морозний дивовижний

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

1
Я погоджуюсь з коментарем про те, що не використовую префікс "I" для інтерфейсів так само, як я б не рекомендував використовувати суфікс "impl" для реалізацій, це гарна стаття, що роздумує про ці випадки: octoperf.com/blog/2016 /
10/27

19

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

Давайте розглянемо цей конкретний приклад.

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

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

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


Враховуючи тег "Java" у питанні - що означає " загальнодоступний пакет" у Java? Або ваша відповідь не стосується Java? Я думав, що лише члени пакету (такі як класи, інтерфейси та їх учасники), а не самі пакунки, мають специфікатори контролю доступу, такі як 'public', 'private' тощо (Це обговорюється далі @ question stackoverflow.com/questions/4388715/… )
bacar

Я просто уточнив формулювання; під "публічно викритим пакетом" я справді мав на увазі "пакетом з публічно викритими речами".
cjs

Напевно, усі пакунки повинні мати загальнодоступні речі; якщо ні, то як можна використовувати пакет?
bacar

У цьому конкретному випадку існували пакунки, в яких нічого не було загальнодоступним, але які використовувались пакетами, які робили речі загальнодоступними. Отже, перше не можна використовувати безпосередньо, а лише опосередковано через друге.
cjs

1
Але стосується того самого питання. Як пакети, які оприлюднили речі, можуть використовувати пакети, які нічого не оприлюднили? Я не знаю жодного способу застосування на Java "першого не можна використовувати безпосередньо, а лише опосередковано через другий". Якщо у вашому пакеті реалізації є лише приватні члени, він так само недоступний для вашого пакету інтерфейсу, як і для клієнта. & аналогічно для інших областей доступу. У Java немає еквівалента friend(C ++) або internal(C #).
bacar

10

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


7

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


7
Ви можете мати проект API окремо (будучи окремим дистрибутивом), і це все одно не зупиняє інтерфейси та реалізацію в одному пакеті. Трохи схожий на те, що у вас є модульні тести в тому ж пакеті, що і тестовані класи, але все ще в окремому артефакті збірки. Банки та пакети в Java набагато ортогональніші.
Джордж

7

Книга " Практична інженерія програмного забезпечення: підхід до вивчення конкретних випадків" виступає за введення інтерфейсів в окремі проекти / пакети.

Архітектура PCMEF +, про яку йдеться в книзі, має такі принципи:

  1. Принцип низхідної залежності (DDP)
  2. Принцип вищого сповіщення (UNP)
  3. Принцип сусідського спілкування (NCP)
  4. Приблизний принцип асоціації (EAP)
  5. Принцип ліквідації циклу (CEP)
  6. Принцип іменування класів (CNP)
  7. Принцип пакету знайомств (APP)

Опис принципів №3 та №7 пояснює, чому це гарна ідея:

Принцип комунікаційного сусідства вимагає, щоб пакет міг спілкуватися лише безпосередньо зі своїм сусіднім пакетом. Цей принцип гарантує, що система не розпадається на нестисливу мережу об'єктів взаємозв'язку. Для забезпечення цього принципу передача повідомлень між сусідніми об'єктами використовує делегування (Розділ 9.1.5.1). У більш складних сценаріях пакет знайомств (Розділ 9.1.8.2) може бути використаний для групування інтерфейсів для допомоги у співпраці, яка залучає віддалені пакети.

Принцип пакету знайомств є наслідком принципу сусідського спілкування. Пакет знайомства складається з інтерфейсів, які об'єкт передає замість конкретних об'єктів в аргументах до викликів методів. Інтерфейси можуть бути реалізовані в будь-якому пакеті PCMEF. Це ефективно дозволяє спілкуватися між сусідніми пакетами, одночасно централізуючи управління залежностями до одного пакету ознайомлення. Потреба в пакеті знайомств була роз’яснена в Розділі 9.1.8.2 і обговорена далі в контексті PCMEF.

Дивіться це посилання: http://comp.mq.edu.au/books/pse/about_book/Ch9.pdf


5

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


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

2
У будь-якому випадку це не є великим тягарем ...
Пол Соньє,

4
-1 Так, це так. Це надмірно ускладнює пошук реалізації та відокремлює речі, які належать разом.
Майкл Борґвардт,

5
А не публікація реалізацій - це зовсім інша справа, яку можна зробити, зробивши їх приватно приватними - і НЕ можна зробити, помістивши їх в окремий пакет. Не існує такого поняття, як непублічний пакет.
Майкл Борґвардт,

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

4

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


7
Як це допомагає вам змінити реалізації?
Януш,

дивіться відповідь Кемерона Поупа, це саме причина того, що ми робимо це тут
hhafez

3

Я не маю великого досвіду роботи з Java, але мені подобається робити це як хорошу практику в C # /. NET, оскільки це дозволяє розширити в майбутньому, де збірки з конкретними класами, що реалізують інтерфейси, можуть не розподілятися аж до клієнта, оскільки вони проксі-сервером заводу-посередника або через провід у сценарії веб-служби.


2

Я зазвичай ставлю інтерфейси з реалізацією, однак я можу начебто зрозуміти, чому ви можете тримати їх окремо. Скажімо, наприклад, хтось хотів змінити класи на основі ваших інтерфейсів, їм знадобиться jar / lib / etc з вашою реалізацією, а не просто інтерфейси. З їх окремими ви можете просто сказати "Ось моя реалізація цього інтерфейсу" і закінчити з цим. Як я вже сказав, не те, що я роблю, але я можу зрозуміти, чому деякі можуть цього захотіти.


2

Я роблю це за проектом, і він у мене працює добре з цих причин:

  • Окремо упаковані інтерфейси та реалізації призначені для іншого випадку використання, ніж для різних типів Map або Set. Немає причин мати пакет лише для дерев (java.util.tree.Map, java.util.tree.Set). Це просто стандартна структура даних, тому покладіть її разом з іншими структурами даних. Однак, якщо ви маєте справу з грою-головоломкою, яка має дуже простий інтерфейс налагодження та гарний робочий інтерфейс, як частина її інтерфейсу ви можете мати com.your.app.skin.debug та com.your.app. .шкіра.досить. Я б не поклав їх в один пакет, оскільки вони роблять різні речі, і я знаю, що вдався б до якоїсь SmurfNamingConvention (DebugAttackSurface, DebugDefenceSurface, PrettyAttackSurface тощо), щоб створити якийсь неформальний простір імен для двох, якщо вони були в одному пакеті.

  • Моє вирішення проблеми пошуку пов'язаних інтерфейсів та реалізацій, які знаходяться в окремих пакетах, полягає у прийнятті конфігурації іменування для моїх пакетів. Наприклад, я можу мати всі свої інтерфейси в com.your.app.skin.framework і знати, що інші пакети на тому ж рівні дерева пакетів є реалізаціями. Недоліком є ​​те, що це нетрадиційна конвенція. Чесно кажучи, через 6 місяців я побачу, наскільки хороша ця конвенція :)

  • Я не використовую цю техніку релігійно. Є інтерфейси, які мають сенс лише в певній реалізації. Я не вкладаю їх у фреймворк-пакет. Є деякі пакети, де не схоже, що я збираюся створити 40 різних класів реалізації, тому я не турбуюся.

  • Моя програма використовує guice і має дуже багато інтерфейсів.

Питання розробки програми, як правило, мають переваги та недоліки, і немає однозначної відповіді на всі. Наприклад, чому б ви коли-небудь використовували цю техніку в крихітній програмі на 200 рядків? Для мене це має сенс, враховуючи інші мої архітектурні рішення, для моєї конкретної проблеми. :)


1

Я віддаю перевагу їм в одній упаковці. Немає сенсу створювати пакет спеціально для інтерфейсів. Це особливо непотрібно для команд, які просто люблять свої префікси та суфікси інтерфейсу (наприклад, "I" та "Impl" для Java).

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


0

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

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


0

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


-1

Є багато вагомих причин для розміщення інтерфейсів в окремому пакеті від реалізації. Наприклад, вам може знадобитися використовувати контейнер, який підтримує ін'єкцію залежності, щоб підключити необхідні реалізації під час виконання, і в цьому випадку під час побудови повинен бути присутній лише інтерфейс, а реалізація може бути надана під час виконання. Крім того, ви можете надати декілька реалізацій (наприклад, використовуючи фіктивні реалізації для тестування) або кілька версій певної реалізації під час виконання (наприклад, для тестування A / B тощо). Для таких випадків використання зручніше упаковувати інтерфейс та реалізацію окремо.


Вам не потрібно поміщати реалізацію в інший пакет, щоб зробити щось із перерахованого
Кроуі

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