Какую стратегию вы используете для именования пакетов в проектах Java и почему?

Я подумал об этом некоторое время назад, и он недавно появился, поскольку мой магазин делает свое первое реальное веб-приложение Java.

В качестве вступления я вижу две основные стратегии именования пакетов. (Чтобы быть ясным, я не имею в виду всю "domain.company.project" часть этого, я говорю о соглашении о пакете под этим.) Во всяком случае, соглашения об именах пакетов, которые я вижу, следующие:

  • Функциональность: присвоение ваших пакетов по их функциям по архитектуре, а не их идентификация в соответствии с бизнес-доменом. Другим термином для этого может быть именование в соответствии с "уровнем". Таким образом, у вас будет пакет *.ui и пакет *.domain и пакет *.orm. Ваши пакеты представляют собой горизонтальные срезы, а не вертикальные.

    Это гораздо чаще, чем логическое именование. На самом деле, я не верю, что когда-либо видел или слышал о проекте, который делает это. Это, конечно, заставляет меня лелеять (вроде как думать, что вы придумали решение проблемы NP), поскольку я не ужасно умный, и я полагаю, у всех должны быть большие причины для этого, как они это делают. С другой стороны, я не против того, чтобы люди просто скучали по слону в комнате, и я никогда не слышал, чтобы был фактический аргумент за то, что он назвал так. Это просто стандарт де-факто.

  • Логическое: присвоение имен вашим пакетам в соответствии с их идентификатором бизнес-домена и размещение каждого класса, связанного с этим вертикальным фрагментом функциональности, в этот пакет.

    Я никогда не видел и не слышал об этом, как я упоминал ранее, но для меня это имеет смысл.

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

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

    • Похоже, это позволит вам значительно улучшить преимущества модификаторов доступа Java. Это позволяет вам гораздо более четко определять интерфейсы в подсистемах, а не в слоях системы. Поэтому, если у вас есть подсистема заказов, в которой вы хотите быть прозрачно стойкой, теоретически вы никогда не позволяете чему-либо еще знать, что она устойчива, не создавая публичные интерфейсы для своих классов персистентности на уровне dao и вместо этого упаковывая dao-класс в с только классами, с которыми он имеет дело. Очевидно, что если вы хотите раскрывать эту функциональность, вы можете предоставить ему интерфейс или сделать его общедоступным. Просто кажется, что вы потеряете много из этого, имея вертикальный срез ваших системных функций, разделенных на несколько пакетов.

    • Я полагаю, что один недостаток, который я могу видеть, заключается в том, что он немного усложняет процесс вырезания слоев. Вместо того, чтобы просто удалять или переименовывать пакет, а затем отбрасывать новый с помощью альтернативной технологии, вам нужно войти и изменить все классы во всех пакетах. Тем не менее, я не вижу, что это большое дело. Это может быть из-за отсутствия опыта, но я должен представить, что количество раз, когда вы меняете технологии, бледнеет по сравнению с количеством раз, когда вы входите, и редактируйте вертикальные фрагменты функции в вашей системе.

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

Ответ 1

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

Есть несколько дополнительных правил:

  • слои укладываются из самых общих (снизу) в наиболее конкретные (сверху)
  • каждый слой имеет открытый интерфейс (абстракция)
  • уровень может зависеть только от открытого интерфейса другого уровня (инкапсуляция)
  • слой может зависеть только от более общих слоев (зависимости сверху донизу)
  • слой предпочтительно зависит от слоя непосредственно под ним.

Итак, для веб-приложения, например, у вас могут быть следующие уровни в уровне приложения (сверху вниз):

  • Уровень представления: генерирует пользовательский интерфейс, который будет отображаться на уровне клиента
  • прикладной уровень: содержит логику, специфичную для приложения, состояние
  • уровень сервиса: группирует функциональность по домену, без гражданства
  • уровень интеграции: обеспечивает доступ к базовому уровню (db, jms, email,...)

Для результирующей компоновки пакета это несколько дополнительных правил:

  • корень каждого имени пакета <prefix.company>.<appname>.<layer>
  • интерфейс слоя далее разделяется функциональностью: <root>.<logic>
  • частная реализация слоя имеет префикс private: <root>.private

Вот пример макета.

Уровень презентации разделяется по технологии просмотра и, необязательно, (группами) приложений.

com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...

Уровень приложения делится на варианты использования.

com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...

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

com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...

Уровень интеграции делится на "технологии" и объекты доступа.

com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...

Преимущества разделения пакетов, как это, заключается в том, что легче управлять сложностью, и это увеличивает возможности тестирования и повторного использования. Хотя мне кажется, что это накладные расходы, по моему опыту, это на самом деле очень естественно, и каждый, кто работает над этой структурой (или подобным), забирает ее в течение нескольких дней.

Почему я думаю, что вертикальный подход не так хорош?

В многоуровневой модели несколько разных высокоуровневых модулей могут использовать один и тот же модуль нижнего уровня. Например: вы можете создавать несколько представлений для одного и того же приложения, несколько приложений могут использовать одну и ту же услугу, несколько служб могут использовать один и тот же шлюз. Фокус в том, что при перемещении по слоям изменяется уровень функциональности. Модули в более конкретных слоях не отображают 1-1 на модулях из более общего слоя, потому что уровни функциональности, которые они выражают, не отображают 1-1.

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

Ответ 2

Я считаю, что придерживаюсь принципов дизайна упаковки для дяди Боба . Короче говоря, классы, которые должны быть повторно использованы вместе и изменены вместе (по той же причине, например, изменение зависимости или изменение каркаса), должны быть помещены в один и тот же пакет. ИМО, функциональная разбивка будет иметь больше шансов на достижение этих целей, чем вертикальное/деловое распад в большинстве приложений.

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

Очевидно, что не все виды программного обеспечения одинаковы, и вертикальная разбивка может иметь смысл (с точки зрения достижения целей повторного использования и близкой к изменению) в некоторых проектах.

Ответ 3

Обычно существуют оба уровня деления. Сверху есть единицы развертывания. Они называются "логически" (в ваших словах, думаю, функции Eclipse). Внутри блока развертывания у вас есть функциональное разделение пакетов (думаю, плагины Eclipse).

Например, функция com.feature, и она состоит из плагинов com.feature.client, com.feature.core и com.feature.ui. Внутри плагинов у меня очень мало деления на другие пакеты, хотя это и не необычно.

Обновление: Кстати, есть замечательный разговор Юргена Хуллера о организации кода в InfoQ: http://www.infoq.com/presentations/code-organization-large-projects, Юрген является одним из архитекторов Spring и много знает об этом.

Ответ 4

Большинство проектов java, с которыми я работал, сначала разбивают пакеты Java, а затем логически.

Обычно части достаточно велики, что они разбиты на отдельные артефакты сборки, где вы можете поместить основные функциональные возможности в одну банку, apis в другую, веб-интерфейс в warfile и т.д.

Ответ 5

Пакеты должны быть скомпилированы и распределены как единое целое. Рассматривая, какие классы принадлежат пакету, одним из ключевых критериев является его зависимость. От каких других пакетов (включая сторонние библиотеки) зависит этот класс. Хорошо организованная система будет кластеризовать классы с одинаковыми зависимостями в пакете. Это ограничивает влияние изменения в одной библиотеке, поскольку от него будет зависеть только несколько хорошо определенных пакетов.

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

Ответ 6

Я лично предпочитаю группировать классы логически, тогда внутри них есть подпакет для каждого функционального участия.

Цели упаковки

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

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

Пример моей логической структуры упаковки

В целях иллюстрации можно назвать два модуля - плохо использовать модуль имен как концепцию, которая группирует классы под определенной ветвью дерева папок.

apple.model Apple Store banana.model banana.store

Преимущества

Клиент, использующий Banana.store.BananaStore, доступен только тем функциям, которые мы хотим сделать доступными. Версия спящего режима представляет собой деталь реализации, которую им не нужно знать, и они не должны видеть эти классы, поскольку они добавляют беспорядок в операции хранения.

Другие логические v Функциональные преимущества

Чем дальше к корню, тем шире становится область действия, и вещи, принадлежащие одному пакету, начинают проявлять все больше зависимостей от вещей, принадлежащих к модулям toher. Если кто-то должен был рассмотреть, например, "банановый" модуль, большинство зависимостей ограничивались бы внутри этого модуля. Фактически большинство помощников под "бананом" вообще не будут ссылаться за пределы этого пакета.

Почему функциональность?

Какое значение достигается за счет скидок на основе функциональности. Большинство классов в таком случае не зависят друг от друга, при этом мало или вообще не нужно использовать частные методы или классы пакета. Рефакторинг их таким образом в свои собственные подпакеты немного выгоден, но помогает уменьшить беспорядок.

Разработчик меняет систему

Когда разработчикам поручено внести изменения, которые немного более чем тривиальны, кажется глупым, что потенциально они имеют изменения, которые включают файлы из всех областей дерева пакетов. При логическом структурированном подходе их изменения более локальны в той же части дерева пакетов, которая кажется просто правильной.

Ответ 7

Оба метода функциональный (архитектурный) и логический (функция) подход к упаковке имеют место. Многие примеры приложений (те, которые содержатся в текстовых книгах и т.д.) Соответствуют функциональному подходу размещения презентаций, бизнес-сервисов, сопоставления данных и других архитектурных слоев в отдельные пакеты. В примерах приложений каждый пакет часто имеет только несколько или только один класс.

Этот первоначальный подход прекрасен, поскольку надуманный пример часто служит для: 1) концептуально отображает архитектуру представленной структуры; 2) делается с единственной логической целью (например, добавлять/удалять/обновлять/удалять домашних животных из клиника). Проблема в том, что многие читатели воспринимают это как стандарт, который не имеет границ.

По мере того, как приложение "бизнес" расширяется, чтобы включать все больше функций, следующий подход становится бременем. Хотя я знаю, где искать типы на основе уровня архитектуры (например, веб-контроллеры под пакетом "web" или "ui" и т.д.), Разработка одной логической функции начинает требовать перескакивания назад и вперед между многими пакетами. Это громоздко, по крайней мере, но хуже, чем это.

Поскольку логически связанные типы не упакованы вместе, API слишком широко известен; взаимодействие между логически связанными типами вынуждено быть "общедоступным", чтобы типы могли импортировать и взаимодействовать друг с другом (возможность минимизации видимости по умолчанию/пакета теряется).

Если я создаю библиотеку фреймворка, все мои пакеты будут следовать принципу функциональной/архитектурной упаковки. Мои пользователи API могут даже оценить, что их операторы импорта содержат интуитивно понятный пакет, названный в честь архитектуры.

И наоборот, при создании бизнес-приложения я буду устанавливать пакет по функциям. У меня нет проблем с размещением Widget, WidgetService и WidgetController в одном и том же "com.myorg.widget". пакета, а затем воспользовавшись видимостью по умолчанию (и имея меньше операторов импорта, а также зависимости между пакетами).

Существуют, однако, перекрестные случаи. Если мой WidgetService используется многими логическими доменами (функциями), я могу создать "com.myorg.common.service". пакет. Также есть хороший шанс, что я создаю классы с намерением быть повторно используемыми по всем функциям и в конечном итоге с такими пакетами, как "com.myorg.common.ui.helpers". и "com.myorg.common.util.". Я даже могу переместить все эти более поздние "общие" классы в отдельный проект и включить их в мое деловое приложение как зависимость myorg-commons.jar.

Ответ 8

Это зависит от детализации ваших логических процессов?

Если они автономны, у вас часто есть новый проект для них в исходном управлении, а не в новом пакете.

Проект, на котором я сейчас работаю, ошибочен в логическом разбиении, есть пакет для аспекта jython, пакет для механизма правил, пакеты для foo, bar, binglewozzle и т.д. Я смотрю, что XML-синтаксические парсеры/сценаристы для каждого модуля внутри этого пакета, вместо того, чтобы иметь пакет XML (который я сделал ранее), хотя по-прежнему будет основной пакет XML, в котором идет общая логика. Одна из причин этого, однако, состоит в том, что он может быть расширяемым (плагины), и поэтому каждому плагину необходимо будет также определить его код XML (или базы данных и т.д.), Поэтому централизация этого может впоследствии вызвать проблемы.

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

Для чего нужны помеченные пространства имен. Парсер XML для некоторых функций Jython может быть помечен как Jython, так и XML, вместо того, чтобы выбирать один или другой.

Или, может быть, я болтаю.

Ответ 9

Я бы лично пошел на функциональное именование. Краткая причина: он избегает кодового кошмара кода или кошмара зависимости.

Позвольте мне кое-что уточнить. Что происходит, когда вы используете внешний файл jar с собственным деревом пакетов? Вы эффективно импортируете (скомпилированный) код в свой проект и вместе с ним (с функциональным разделением). Имеет ли смысл использовать два соглашения об именах одновременно? Нет, если это не скрыто от вас. И это, если ваш проект достаточно мал и имеет один компонент. Но если у вас несколько логических единиц, вы, вероятно, не захотите повторно реализовать, скажем, модуль загрузки файлов данных. Вы хотите поделиться им между логическими единицами, не иметь искусственных зависимостей между логически несвязанными единицами, и не нужно выбирать, на каком блоке вы собираетесь поместить этот конкретный общий инструмент.

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

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

  • Если вам нужно ловить рыбу в старых классах, чтобы изменить функциональность, когда у вас есть смена планов, это признак плохой абстракции: вы должны создавать классы, которые обеспечивают четко определенную функциональность, определяемую одним коротким предложением. Только несколько классов высшего уровня должны собрать все это, чтобы отразить ваш бизнес-анализ. Таким образом, вы сможете повторно использовать больше кода, упростить обслуживание, более четкую документацию и меньше проблем с зависимостями.

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

  • Модификаторы доступа - это хороший способ разрешить другим классам понимать вашу обработку для доступа к внутренним компонентам вашего класса. Логические отношения не означают понимания алгоритмических или concurrency ограничений. Функциональный может, хотя это не так. Я очень устал от модификаторов доступа, кроме публичных и частных, потому что они часто скрывают отсутствие надлежащей архитектуры и абстракции класса.

  • В крупных коммерческих проектах изменение технологий происходит чаще, чем вы могли бы поверить. Например, мне пришлось изменить 3 раза уже XML-парсер, 2 раза технологии кеширования и 2 раза программного обеспечения геолокации. Хорошо, что я спрятал все подробные детали в специальном пакете...

Ответ 10

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

Для меня это намного проще поддерживать и визуализировать в вертикальной системе имен, а не в горизонтальной плоскости. if component1.display имеет ссылку на компонент2.dataaccess, который выдает больше предупреждающих колоколов, чем если display.component1 имеет ссылку на dataaccess. component2.

Конечно, компоненты, разделяемые обоими, входят в их собственный пакет.

Ответ 11

Я полностью следую и предлагаю логическую ( "по-особенную" ) организацию! Пакет должен как можно ближе следовать концепции "модуля". Функциональная организация может распространять модуль над проектом, что приводит к меньшему количеству инкапсуляции и подвержено изменениям в деталях реализации.

Возьмем, например, плагин Eclipse: размещение всех представлений или действий в одном пакете будет бесполезным. Вместо этого каждый компонент функции должен перейти в пакет функций или, если их много, в подпакеты (featureA.handlers, featureA.preferences и т.д.).

Конечно, проблема заключается в иерархической системе пакетов (в том числе и в Java), что делает невозможным или, по крайней мере, очень сложное обращение с ортогональными задачами, хотя они встречаются повсюду!

Ответ 12

С чисто практической точки зрения конструкции видимости java позволяют классам в одном пакете обращаться к методам и свойствам с видимостью protected и default, а также с тегами public. Использование непубличных методов с совершенно другого уровня кода, безусловно, будет большим запахом кода. Поэтому я склонен помещать классы из одного слоя в один и тот же пакет.

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

Ответ 13

Это зависит. В моей работе мы иногда разделяли пакеты по функциям (доступ к данным, аналитика) или по классу активов (кредит, акции, процентные ставки). Просто выберите структуру, которая наиболее удобна для вашей команды.

Ответ 14

Интересным экспериментом является не использование пакетов вообще (кроме корневого пакета.)

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

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

Ответ 15

По моему опыту, повторная юзабилити создает больше проблем, чем решение. С новейшими и дешевыми процессорами и памятью я бы предпочел дублирование кода, а не плотную интеграцию для повторного использования.