Как мне структурировать Java-приложение, куда мне поместить мои классы?
Прежде всего, я знаю, как создать приложение Java. Но я всегда был озадачен тем, где поставить свои занятия. Существуют сторонники организации пакетов в строго предметно-ориентированной манере, другие по уровням.
У меня самого всегда были проблемы с
- именование,
- размещение
Так,
- Куда вы помещаете специфичные для вашего домена константы (и какое имя лучше всего подходит для такого класса)?
- Где вы помещаете классы для вещей, которые являются как инфраструктурными, так и специфичными для домена (например, у меня есть класс FileStorageStrategy, который хранит файлы либо в базе данных, либо, альтернативно, в базе данных)?
- Где поставить исключения?
- Есть ли стандарты, на которые я могу сослаться?
10 ответов
Мне действительно нравится стандартная структура каталогов Maven.
Одна из ключевых идей для меня - иметь два исходных корня - один для производственного кода и один для тестового кода, например:
MyProject/src/main/java/com/acme/Widget.java
MyProject/src/test/java/com/acme/WidgetTest.java
(здесь src/main/java и src/test/java являются исходными корнями).
Преимущества:
- Ваши тесты имеют пакетный (или "по умолчанию") уровень доступа к тестируемым классам.
- Вы можете легко упаковать только ваши производственные исходники в JAR, сбросив src/test/java в качестве исходного корня.
Одно правило о размещении классов и пакетов:
Вообще говоря, хорошо структурированные проекты будут свободны от циклических зависимостей. Узнайте, когда они плохие (а когда нет), и подумайте о таком инструменте, как JDepend или SonarJ, который поможет вам устранить их.
Я большой поклонник организованных источников, поэтому я всегда создаю следующую структуру каталогов:
/src - for your packages & classes
/test - for unit tests
/docs - for documentation, generated and manually edited
/lib - 3rd party libraries
/etc - unrelated stuff
/bin (or /classes) - compiled classes, output of your compile
/dist - for distribution packages, hopefully auto generated by a build system
В /src я использую шаблоны Java по умолчанию: имена пакетов, начинающиеся с вашего домена (org.yourdomain.yourprojectname) и имена классов, отражающие аспект ООП, который вы создаете с помощью класса (см. Другие комментарии). Также полезны общие имена пакетов, такие как util, model, view, events.
Я склонен помещать константы для определенной темы в собственный класс, например SessionConstants или ServiceConstants, в один и тот же пакет классов домена.
Там, где я работаю, мы используем Maven 2, и у нас есть довольно хороший архетип для наших проектов. Цель состояла в том, чтобы получить хорошее разделение интересов, поэтому мы определили структуру проекта, используя несколько модулей (по одному для каждого "уровня" приложения): - общий: общий код, используемый другими уровнями (например, i18n) - объекты: домен entity - repositories: этот модуль содержит интерфейсы и реализации daos - services-intf: интерфейсы для сервисов (например, UserService, ...) - services-impl: реализации сервисов (например, UserServiceImpl) - web: все, что касается веб-контент (например, css, jsps, jsf pages, ...) - ws: веб-сервисы
Каждый модуль имеет свои собственные зависимости (например, репозитории могут иметь jpa), а некоторые имеют проектный характер (поэтому они принадлежат общему модулю). Зависимости между различными модулями проекта четко разделяют вещи (например, веб-уровень зависит от уровня сервиса, но не знает о слое хранилища).
Каждый модуль имеет свой собственный базовый пакет, например, если пакет приложения "com.foo.bar", то мы имеем:
com.foo.bar.common
com.foo.bar.entities
com.foo.bar.repositories
com.foo.bar.services
com.foo.bar.services.impl
...
Каждый модуль соответствует стандартной структуре проекта Maven:
src\
..main\java
...\resources
..test\java
...\resources
Модульные тесты для данного слоя легко находят свое место в \src\test... Все, что относится к области, имеет свое место в модуле entity. Теперь что-то вроде FileStorageStrategy должно войти в модуль репозиториев, так как нам не нужно точно знать, что такое реализация. На уровне сервисов мы знаем только интерфейс репозитория, нам все равно, какова конкретная реализация (разделение задач).
У этого подхода есть несколько преимуществ:
- четкое разделение интересов
- каждый модуль может быть упакован как jar (или война в случае веб-модуля) и, таким образом, облегчает повторное использование кода (например, мы могли бы установить модуль в хранилище maven и повторно использовать его в другом проекте)
- максимальная независимость каждой части проекта
Я знаю, что это не отвечает на все ваши вопросы, но я думаю, что это может поставить вас на правильный путь и может оказаться полезным для других.
Имена классов всегда должны быть описательными и не требующими пояснений. Если у вас есть несколько областей ответственности за ваши классы, то, вероятно, их следует реорганизовать.
Аналогично для вас пакеты. Они должны быть сгруппированы по областям ответственности. У каждого домена есть свои исключения.
Как правило, не переживайте, пока не достигнете точки, когда она становится подавляющей и раздутой. Тогда садитесь и не кодируйте, просто реорганизуйте классы, регулярно компилируя, чтобы убедиться, что все работает. Затем продолжайте, как вы делали раньше.
Одна вещь, которую я нашел очень полезной для модульных тестов, это наличие каталогов myApp / src /, а также myApp / test_src /. Таким образом, я могу поместить модульные тесты в те же пакеты, что и классы, которые они тестируют, и все же я могу легко исключить тестовые случаи при подготовке производственной установки.
Краткий ответ: нарисуйте архитектуру вашей системы в терминах модулей, нарисованных рядом, с каждым модулем, разрезанным вертикально на слои (например, представление, модель, постоянство). Затем используйте структуру, такую как com.mycompany.myapp.somemodule.somelayer, например com.mycompany.myapp.client.view или com.mycompany.myapp.server.model.
Использование пакетов верхнего уровня для прикладных модулей в старомодном компьютерном понимании модульного программирования должно быть очевидным. Тем не менее, в большинстве проектов, над которыми я работал, мы забываем об этом и в итоге получаем беспорядок пакетов без этой структуры верхнего уровня. Этот анти-шаблон обычно показывает себя как пакет для чего-то вроде "слушателей" или "действий", которые группируют несвязанные классы просто потому, что они реализуют один и тот же интерфейс.
Внутри модуля или небольшого приложения используйте пакеты для прикладных уровней. Вероятные пакеты включают в себя следующие вещи, в зависимости от архитектуры:
- com.mycompany.myapp.view
- com.mycompany.myapp.model
- com.mycompany.myapp.services
- com.mycompany.myapp.rules
- com.mycompany.myapp.persistence (или 'dao' для уровня доступа к данным)
- com.mycompany.myapp.util (остерегайтесь этого, как если бы оно было "разное")
Внутри каждого из этих уровней естественно группировать классы по типу, если их много. Обычным антишаблоном здесь является излишнее представление слишком большого количества пакетов и уровней подпакета, чтобы в каждом пакете было только несколько классов.
Используйте пакеты, чтобы сгруппировать связанные функции вместе.
Обычно вершина вашего дерева пакетов - это ваше доменное имя в обратном порядке (com.domain.subdomain
), чтобы гарантировать уникальность, и тогда обычно будет пакет для вашего приложения. Затем поделите это на связанную область, так что ваш FileStorageStrategy
может войти, скажем, com.domain.subdomain.myapp.storage
и тогда могут быть конкретные реализации / подклассы / что угодно в com.domain.subdomain.myapp.storage.file
а также com.domain.subdomain.myapp.storage.database
, Эти имена могут быть довольно длинными, но import
хранит их все в верхней части файлов, и IDE могут помочь в этом.
Исключения обычно идут в том же пакете, что и классы, которые их генерируют, так что если у вас есть, скажем, FileStorageException
это будет идти в той же упаковке, что и FileStorageStrategy
, Аналогично, интерфейс, определяющий константы, будет в том же пакете.
Там нет никакого стандарта как такового, просто используйте здравый смысл, и если все становится слишком грязно, рефакторинг!
Я думаю, будь проще и не думай слишком. Не переусердствуйте с абстракцией и не слишком много. Просто держите его аккуратно, и по мере роста рефакторинг становится тривиальным. Одна из лучших функций IDE - это рефакторинг, так почему бы не использовать его и сэкономить свои мозги для решения проблем, связанных с вашим приложением, а не проблем мета, таких как организация кода.
Одна вещь, которую я сделал в прошлом - если я продолжу занятия, я постараюсь следовать их правилам. Например, при работе с Spring Framework мои классы MVC Controller будут храниться в пакете с именем com.mydomain.myapp.web.servlet.mvc. Если я не расширяю что-то, я просто делаю то, что проще. com.mydomain.domain для доменных объектов (хотя, если у вас есть тонна доменных объектов, этот пакет может быть немного громоздким). Для констант, специфичных для предметной области, я на самом деле помещаю их как открытые константы в наиболее связанный класс Например, если у меня есть класс "Member" и максимальная константа длины имени члена, я помещаю его в класс Member. В некоторых магазинах есть отдельный класс Constants, но я не вижу смысла в том, чтобы объединять несвязанные числа и строки в один класс. Я видел, как некоторые другие магазины пытаются решить эту проблему, создавая классы SEPARATE Constants, но это кажется пустой тратой времени, а результат слишком запутанный. Используя эту настройку, большой проект с несколькими разработчиками будет дублировать константы повсюду.
Мне нравится разбивать мои классы на пакеты, которые связаны друг с другом.
Например:модель для вызовов, связанных с базой данных
Посмотреть классы, которые имеют дело с тем, что вы видите
Функциональные классыControl Core
Util Любой разный. используемые классы (обычно статические функции)
и т.п.