Я помню примерно 15 лет назад, когда я начал изучать Java. Я много читал об этом «пакете» и «пространствах имен», и я полностью не понял его. Грустная вещь: в то время как некоторые аспекты пакета понятны почти всем в отрасли, другие не понимают. Итак, давайте посмотрим, для чего подходят пакеты.
Пространства имен:
Приставляя все ваши пакеты к домену под вашим контролем, вы убедитесь, что имена ваших классов уникальны. Это важно для успеха невероятного количества проектов с открытым исходным кодом. Каждый проект может (и, возможно, на каком-то этапе) определяет класс «Фильтр», не мешая этому классу взаимодействовать со всеми другими классами с таким же именем (кроме плохого разработчика, который скопировал некоторый код без операторов импорта из Интернета и теперь имеет чтобы выяснить, на какой класс Filter на самом деле ссылались). Это довольно хорошо понято, и я давно не видел соответствующего использования корневого пакета.
Организация:
У моего сына огромная коробка с кубиками лего. Вероятно, несколько тысяч, может быть, десятки тысяч из них. Когда он ищет простой кирпич 2х4, это не проблема. Но когда он ищет этот особенный кирпич, который существует только 4 раза в коллекции или хотя бы один раз !? Это может занять много времени, чтобы найти его. Сравните это с аптечным кабинетом. Сотни лекарств, и обычно требуется всего несколько секунд, чтобы найти правильный. И они даже не используют Google для этого! У них просто есть строгий принцип упорядочения каждого лекарства, включая правило, определяющее правильную коробку для нового лекарства. Поскольку все участники знают этот принцип, легко определить правильную коробку, в которой находится лекарство. Такой принцип упорядочения чрезвычайно полезен, когда он установлен в начале проекта.
При определении такого принципа одного критерия в большинстве случаев недостаточно. Но если вы используете больше, убедитесь, что они не мешают, сделав правила ортогональными. Это означает, что у нас нет правила, гласящего: «Весь код доступа к базе данных должен идти в пакете x», а другое правило, гласящее: «Весь код, связанный с клиентами, должен входить в пакет y». В противном случае вы не будете знать, где разместить CustomerDAO. Вместо этого примените ортогональные правила на разных глубинах дерева пакетов. Моя структура пакета по умолчанию выглядит следующим образом:
1
|
<organisational-prefix>.<application>.<deployment-unit>.<module>.<layer>.<optional further substructure if needed> |
Это приводит к именам пакетов, таким как com.mycompany.theCoolApp.server.user.persistence
или com.mycompany.theCoolApp.client.shoppingCart.presentation
.
Если вы посмотрите на такую структуру пакета, становится совершенно очевидно, где принадлежит новый класс или где искать, если что-то подобное уже существует. Это становится еще лучше, когда вы избегаете названий, таких как util
или misc
которые могут скрывать более или менее все. Также вы можете посмотреть на эти пакеты и сразу же узнать что-то об архитектуре. Как только вы увидите уровень пакетов с именами client
, webserver
и batchserver
вы сформируете модель в своей голове, как структурировано приложение, и если имена выбраны правильно, это, вероятно, близко к реальной вещи. Поскольку в каждом module
применяются одинаковые правила для layers
вы можете узнать больше о структуре приложения в нижних пакетах.
Промежуточный module
сообщает тип домена, с которым имеет дело приложение. Вполне естественно, что важные концепции получают свой собственный пакет и тем самым делают заявление для всех, кто проверяет код: это важное понятие в этом приложении.
Мне также нравится добавлять правило «Пакет должен содержать классы a
— b
, но не должен содержать c
или более» с соответствующими значениями для a, b и c. Это заставляет создавать новые пакеты по мере роста приложения, сохраняя каждый пакет до управляемого размера.
Конечно, в небольших приложениях структура может быть уменьшена. Например, если имеется только один модуль развертывания, для этой классификации не требуется отдельный уровень пакета.
Последнее использование пакета является наиболее игнорируемым: промежуточный блок моделирования : Joe Average Developer в основном занимается классами, методами и отдельными строками кода, пытаясь придумать структуру кода на том уровне, который соответствует потребностям приложения. , Часто есть какой-то архитектор, который выясняет, как развернуть приложение, и, таким образом, определяет необходимые единицы развертывания (представьте отдельные jar-файлы). Если вы посмотрите на масштаб этих артефактов, вам может показаться что-то интересное:
- 1 метод состоит примерно из 10 строк кода.
- 1 класс состоит примерно из 10 методов.
- 1 банка состоит из примерно 100 — 1000 классов.
Если никто не заботится о пакетах, то по крайней мере один, часто два уровня структуры отсутствуют! Этот пробел можно и нужно заполнять посылками. Это не только означает, что пакеты должны существовать и иметь разумный размер, это также означает, что они должны придерживаться общих руководящих принципов проектирования. Особенно принцип единой ответственности и правильная обработка зависимостей:
Принцип единой ответственности:
С предложенной выше схемой именования проделана большая работа по соблюдению SRP. Если содержимое пакета делает то, что говорит его название, все в порядке на этом фронте.
Управление зависимостями:
жестче зверя. В настоящее время Java не предлагает надлежащей системы для контроля зависимостей между пакетами и особенно супер-пакетами, то есть пакетами, которые содержат несколько других пакетов. Есть OSGI , но мне было тяжело работать с ним, тем более, что мне никогда не требовались все средства динамической загрузки, но возникали проблемы с загрузчиком классов. Также есть Jigsaw, но его еще нет. Поэтому я предпочитаю собственные тесты для определения и проверки структуры пакета приложений, с которыми я работаю. Мой инструмент выбора — JDepend . Он дает вам списки зависимостей между пакетами, и вы можете использовать их для сравнения с определенными вами правилами. Кто-то создает зависимость от пакета A до пакета B, которая не должна существовать? Бум, тест становится красным.
Итак, каковы полезные правила для зависимостей пакетов? Первый: циклов нет. Не на уровне пакета, но также не на layer
уровня и не на уровне module
как указано выше. Второе: модули и слои имеют строгий порядок, в котором они могут зависеть друг от друга, все остальное запрещено.
Эти правила значительно ограничивают степень свободы разработчика. Но по моему опыту, это выкуривает нарушения принципа единой ответственности, который часто проявляется в виде циклических зависимостей. Например, если у вас есть модуль «Заказ» и модуль «Клиент», вам кажется, что эти двое должны знать друг друга. Если у вас есть Заказ, вы хотите знать, кому он принадлежит. Если у вас есть Клиент, вы должны быть в состоянии сообщить ей Заказы, которые она разместила. Правильно? Да, возможно. Но вам нужны полноценные объекты и функциональность с обеих сторон? Возможно нет. Например, при помощи пакета интерфейса, содержащего только основное ядро для функциональности клиента, необходимого для модуля «Заказ», и отдельного полноценного модуля «Клиент», который содержит ссылки на заказы, можно сломать эти зависимости и добиться более четкого разделения интересов в вашем структура пакета.
Это, в свою очередь, помогает, когда вы пытаетесь развивать свое приложение. То, что сегодня представляет собой пакет, может когда-нибудь превратиться в модуль развертывания, и если у вас есть круговые зависимости между модулями развертывания, у вас возникнут серьезные проблемы. Или, может быть, ваша команда превращается в несколько команд. С чистой структурой пакета, как описано выше, у вас есть очевидные границы, где вы можете разделить, а также очевидные критерии, когда команды должны собираться вместе, чтобы обсудить изменения в пакете, используемом несколькими командами.
Ссылка: Важность пакетов от нашего партнера JCG Йенса Шаудера в блоге Schauderhaft .