Как мне структурировать Java-приложение, где разместить свои классы?

Прежде всего, я знаю, как создать приложение на Java. Но я всегда был озадачен, где разместить свои классы. Одни сторонники организации пакетов строго ориентированы на предметную область, другие - по уровням.

У меня всегда были проблемы с

  • именование
  • размещение

Так,

  1. Куда вы помещаете константы, специфичные для вашего домена (и какое имя лучше всего подходит для такого класса)?
  2. Где вы помещаете классы для вещей, которые относятся как к инфраструктуре, так и к предметной области (например, у меня есть класс FileStorageStrategy, который хранит файлы либо в базе данных, либо, альтернативно, в базе данных)?
  3. Куда ставить исключения?
  4. Есть ли стандарты, на которые я могу сослаться?

Ответов (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 , которые помогут вам их устранить.

Мне нравится разбивать мои классы на пакеты, которые связаны друг с другом.

Например: Модель для вызовов, связанных с базой данных.

Просмотр классов, которые имеют дело с тем, что вы видите

Классы функциональности Control Core

Используйте Any misc. используемые классы (обычно статические функции)

и т.п.

Одна вещь, которую я нашел очень полезной для модульных тестов, - это каталоги myApp / src /, а также myApp / test_src /. Таким образом, я могу размещать модульные тесты в тех же пакетах, что и классы, которые они тестируют, и при этом я могу легко исключить тестовые случаи при подготовке своей производственной установки.

Там, где я работаю, мы используем Maven 2, и у нас есть довольно хороший архетип для наших проектов. Цель состояла в том, чтобы получить хорошее разделение проблем, поэтому мы определили структуру проекта, используя несколько модулей (по одному для каждого «уровня» приложения): - общий: общий код, используемый другими уровнями (например, i18n) - сущности: домен сущности - репозитории: этот модуль содержит интерфейсы и реализации daos - services-intf: интерфейсы для служб (например, UserService, ...) - services-impl: реализации служб (например, UserServiceImpl) - web: все, что касается веб-контент (например, css, jsps, jsf-страницы, ...) - 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 ... Все, что относится к предметной области, имеет свое место в модуле сущностей. Теперь что-то вроде FileStorageStrategy должно войти в модуль репозиториев, поскольку нам не нужно точно знать, что это за реализация. На уровне сервисов мы знаем только интерфейс репозитория, нам все равно, какова конкретная реализация (разделение задач).

У этого подхода есть несколько преимуществ:

  • четкое разделение проблем
  • каждый модуль упаковывается как jar (или война в случае веб-модуля) и, таким образом, позволяет упростить повторное использование кода (например, мы могли бы установить модуль в репозиторий maven и повторно использовать его в другом проекте)
  • максимальная независимость каждой части проекта

Я знаю, что это не отвечает на все ваши вопросы, но я думаю, что это может направить вас на правильный путь и может оказаться полезным для других.

Используйте пакеты, чтобы сгруппировать связанные функции вместе.

Обычно наверху вашего дерева пакетов находится ваше доменное имя reversed ( 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 . Точно так же интерфейс, определяющий константы, будет в одном пакете.

На самом деле нет никакого стандарта как такового, просто используйте здравый смысл, а если все станет слишком запутанным, проведите рефакторинг!

Имена классов всегда должны быть описательными и не требующими пояснений. Если у вас есть несколько доменов ответственности за ваши классы, то их, вероятно, следует реорганизовать.

То же самое и с пакетами. Их следует сгруппировать по сферам ответственности. У каждого домена есть свои исключения.

Как правило, не переживайте, пока не дойдете до точки, когда она станет чрезмерной и раздутой. Затем сядьте и не пишите код, просто реорганизуйте классы, регулярно компилируя, чтобы убедиться, что все работает. Затем продолжайте, как делали раньше.

Одна вещь, которую я делал в прошлом, - если я расширяю класс, я постараюсь следовать их соглашениям. Например, при работе со Spring Framework у меня будут классы контроллера MVC в пакете с именем com.mydomain.myapp.web.servlet.mvc. Если я не расширяю что-то, я просто выбираю самое простое. com.mydomain.domain для объектов домена (хотя, если у вас есть тонна объектов домена, этот пакет может стать немного громоздким). Для констант, специфичных для предметной области, я фактически помещаю их как общедоступные константы в наиболее родственный класс. Например, если у меня есть класс «Член» и константа максимальной длины имени члена, я помещаю его в класс «Член». Некоторые магазины создают отдельный класс констант, но я не вижу смысла объединять несвязанные числа и строки в один класс. Я' Мы видели, как некоторые другие магазины пытаются решить эту проблему, создавая ОТДЕЛЬНЫЕ классы констант, но это кажется пустой тратой времени, а результат слишком запутанным. Используя эту настройку, большой проект с несколькими разработчиками будет дублировать константы повсюду.

Я думаю, что все будет просто и не задумываюсь над этим. Не переусердствуйте и не накладывайте слишком много слоев. Просто держите его в чистоте, и по мере его роста рефакторинг становится тривиальным. Одна из лучших функций IDE - это рефакторинг, так почему бы не воспользоваться им и не сэкономить мозговые ресурсы для решения проблем, связанных с вашим приложением, а не мета-проблем, таких как организация кода.

Я большой поклонник организованных источников, поэтому всегда создаю следующую структуру каталогов:

/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, в одном пакете классов предметной области.

Краткий ответ: нарисуйте архитектуру вашей системы в виде модулей, нарисованных бок о бок, причем каждый модуль будет вертикально разделен на слои (например, вид, модель, постоянство). Затем используйте такую ​​структуру, как 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 (остерегайтесь использования этого, как если бы это было "разное")

Внутри каждого из этих слоев естественно сгруппировать классы по типу, если их много. Распространенным анти-шаблоном здесь является без надобности вводить слишком много пакетов и уровней подпакетов, чтобы в каждом пакете было только несколько классов.