Я думал об этом некоторое время назад, и недавно он всплыл, когда мой магазин делает свое первое настоящее веб-приложение на Java.
В качестве вступления я вижу две основные стратегии именования пакетов. (Для ясности, я не имею в виду всю часть этого «domain.company.project», я говорю о соглашении о пакете под ним.) В любом случае, соглашения об именах пакетов, которые я вижу, следующие:
Функциональность: именование пакетов в соответствии с их архитектурной функцией, а не их идентичностью в соответствии с бизнес-областью. Другой термин для этого может быть именование в соответствии с «слоем». Итак, у вас будет пакет * .ui, пакет * .domain и пакет * .orm. Ваши пакеты представляют собой горизонтальные срезы, а не вертикальные.
Это гораздо более распространено, чем логическое именование. На самом деле, я не верю, что когда-либо видел или слышал о проекте, который делает это. Это, конечно, заставляет меня насторожиться (вроде как думать, что вы придумали решение проблемы NP), поскольку я не очень умен и полагаю, что у всех должны быть веские причины, чтобы делать это так, как они. С другой стороны, я не против того, чтобы люди просто скучали по слону в комнате, и я никогда не слышал реальных аргументов в пользу такого именования пакетов. Это просто стандарт де-факто.
Логика: присвоение имен вашим пакетам в соответствии с идентификаторами их бизнес-домена и включение в этот пакет каждого класса, имеющего отношение к вертикальной части функциональности.
Я никогда не видел и не слышал об этом, как я уже упоминал ранее, но для меня это имеет большой смысл.
Я предпочитаю подходить к системам вертикально, а не горизонтально. Я хочу заняться разработкой системы обработки заказов, а не уровня доступа к данным. Очевидно, есть большая вероятность, что я коснусь уровня доступа к данным при разработке этой системы, но дело в том, что я не думаю об этом так. Это, конечно, означает, что когда я получаю приказ на изменение или хочу реализовать какую-то новую функцию, было бы неплохо не копаться в кучке пакетов, чтобы найти все связанные классы. Вместо этого я просто смотрю в пакет X, потому что то, что я делаю, связано с X.
С точки зрения разработки, я считаю большой победой то, что ваши пакеты документируют вашу бизнес-область, а не вашу архитектуру. Мне кажется, что домен - это почти всегда та часть системы, которую труднее разобрать, поскольку архитектура системы, особенно на данном этапе, становится почти приземленной в ее реализации. Тот факт, что я могу прийти к системе с этим типом соглашения об именах и сразу по именованию пакетов знать, что она имеет дело с заказами, клиентами, предприятиями, продуктами и т. Д., Кажется чертовски удобным.
Похоже, это позволит вам лучше использовать модификаторы доступа Java. Это позволяет гораздо более четко определять интерфейсы в подсистемах, а не в уровнях системы. Поэтому, если у вас есть подсистема заказов, которая должна быть прозрачно постоянной, вы могли бы теоретически никогда не позволять никому другому знать, что она постоянна, не создавая общедоступные интерфейсы для своих классов постоянства на уровне dao и вместо этого упаковывая класс dao в только с классами, с которыми он имеет дело. Очевидно, что если вы хотите раскрыть эту функциональность, вы можете предоставить для нее интерфейс или сделать его общедоступным. Просто кажется, что вы многое теряете из-за того, что вертикальная часть функций вашей системы разделена на несколько пакетов.
Я полагаю, что один недостаток, который я вижу, заключается в том, что он немного затрудняет извлечение слоев. Вместо того, чтобы просто удалить или переименовать пакет, а затем вставить новый с альтернативной технологией, вы должны войти и изменить все классы во всех пакетах. Однако я не вижу в этом особого значения. Возможно, это из-за недостатка опыта, но я должен представить, что количество раз, когда вы меняете технологии, бледнеет по сравнению с количеством раз, когда вы входите и редактируете вертикальные срезы функций в своей системе.
Итак, я думаю, тогда у вас возникнет вопрос, как вы называете свои пакеты и почему? Пожалуйста, поймите, я не обязательно думаю, что наткнулся на золотого гуся или что-то здесь. Я новичок во всем этом, в основном с академическим опытом. Тем не менее, я не могу заметить пробелов в своих рассуждениях, поэтому надеюсь, что вы все сможете, и я смогу двигаться дальше.
источник
Ответы:
Что касается дизайна упаковки, я сначала делю по слоям, а затем по другим функциям.
Есть еще несколько дополнительных правил:
Так, например, для веб-приложения у вас могут быть следующие уровни на уровне приложения (сверху вниз):
Для итогового макета пакета действуют некоторые дополнительные правила:
<prefix.company>.<appname>.<layer>
<root>.<logic>
<root>.private
Вот пример макета.
Уровень представления разделен по технологии просмотра и, необязательно, по (группам) приложений.
Уровень приложения разделен на варианты использования.
Уровень обслуживания разделен на бизнес-домены, на которые влияет логика домена на бэкэнд-уровне.
Уровень интеграции разделен на «технологии» и объекты доступа.
Преимущество такого разделения пакетов заключается в том, что проще управлять сложностью, а также повышается тестируемость и возможность повторного использования. Хотя это кажется большим количеством накладных расходов, по моему опыту, на самом деле это происходит очень естественно, и каждый, кто работает над этой структурой (или подобной), понимает это за считанные дни.
Почему я считаю, что вертикальный подход не так хорош?
В многоуровневой модели несколько разных модулей высокого уровня могут использовать один и тот же модуль более низкого уровня. Например: вы можете создать несколько представлений для одного и того же приложения, несколько приложений могут использовать одну и ту же службу, несколько служб могут использовать один и тот же шлюз. Уловка здесь в том, что при перемещении по слоям меняется уровень функциональности. Модули в более конкретных слоях не отображают 1-1 на модули из более общего слоя, потому что уровни функциональности, которые они выражают, не соответствуют 1-1.
Когда вы используете вертикальный подход к дизайну упаковки, т. Е. Сначала разделяете по функциональности, а затем помещаете все строительные блоки с разными уровнями функциональности в одну и ту же «оболочку функциональности». Вы можете разработать свои общие модули для более конкретных. Но это нарушает важный принцип, согласно которому более общий слой не должен знать о более конкретных слоях. Уровень услуг, например, не следует моделировать на основе концепций прикладного уровня.
источник
Я придерживаюсь принципов дизайна упаковки дяди Боба . Короче говоря, классы, которые должны повторно использоваться вместе и изменяться вместе (по той же причине, например, изменение зависимости или изменение структуры), должны быть помещены в один и тот же пакет. ИМО, функциональная разбивка имела бы больше шансов на достижение этих целей, чем вертикальная / специфическая для бизнеса разбивка в большинстве приложений.
Например, горизонтальный фрагмент объектов домена может быть повторно использован различными типами клиентского интерфейса или даже приложениями, а горизонтальный фрагмент веб-интерфейса может изменяться вместе, когда необходимо изменить базовую веб-структуру. С другой стороны, легко представить себе волновой эффект этих изменений во многих пакетах, если классы из разных функциональных областей сгруппированы в этих пакетах.
Очевидно, что не все виды программного обеспечения одинаковы, и вертикальная разбивка может иметь смысл (с точки зрения достижения целей повторного использования и доступности для изменений) в определенных проектах.
источник
Обычно присутствуют оба уровня разделения. Сверху есть подразделения развертывания. Они названы «логически» (в ваших терминах подумайте о функциях Eclipse). Внутри модуля развертывания у вас есть функциональное разделение пакетов (подумайте о плагинах Eclipse).
Например, функция
com.feature
, и она состоит изcom.feature.client
,com.feature.core
иcom.feature.ui
плагинов. Внутри плагинов у меня очень мало разделения на другие пакеты, хотя это тоже не редкость.Обновление: Кстати, Юрген Хеллер много говорил об организации кода в InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Юрген - один из архитекторов Spring, и знает толк в этом.
источник
Большинство проектов Java, над которыми я работал, сначала нарезают пакеты Java функционально, а затем логически.
Обычно части достаточно велики, чтобы разбить их на отдельные артефакты сборки, где вы можете поместить основные функции в одну банку, API в другую, материалы веб-интерфейса в файл war и т. Д.
источник
Пакеты должны составляться и распространяться как единое целое. При рассмотрении того, какие классы принадлежат пакету, одним из ключевых критериев является его зависимости. От каких других пакетов (включая сторонние библиотеки) зависит этот класс. Хорошо организованная система объединяет классы с похожими зависимостями в пакет. Это ограничивает влияние изменения в одной библиотеке, поскольку от него будут зависеть только несколько четко определенных пакетов.
Похоже, ваша логическая вертикальная система может иметь тенденцию «размазывать» зависимости между большинством пакетов. То есть, если каждая функция упакована как вертикальный фрагмент, каждый пакет будет зависеть от каждой сторонней библиотеки, которую вы используете. Любое изменение библиотеки, вероятно, затронет всю вашу систему.
источник
Лично я предпочитаю логически группировать классы, а не включать в них подпакеты для каждого функционального участия.
Цели упаковки
В конце концов, пакеты предназначены для группировки вещей - идея состоит в том, что классы связаны друг с другом. Если они живут в одном пакете, они могут использовать частный пакет, чтобы ограничить видимость. Проблема в том, что объединение всех ваших представлений и настойчивости в один пакет может привести к смешению множества классов в один пакет. Следующая разумная вещь, которую нужно сделать, - это создать соответственно view, persistance, util sub-пакеты и классы рефакторинга. К сожалению, недостаточно защищенная и закрытая область видимости пакета не поддерживает концепцию текущего пакета и подпакета, поскольку это поможет обеспечить соблюдение таких правил видимости.
Теперь я вижу ценность разделения через функциональность, потому что какое значение имеет группировка всего, что связано с представлением. В этой стратегии именования элементы не связаны с некоторыми классами в представлении, в то время как другие сохраняются и т. Д.
Пример моей логической структуры упаковки
В целях иллюстрации давайте назовем два модуля - я буду использовать имя module как концепцию, которая группирует классы в определенной ветви дерева пакетов.
apple.model apple.store banana.model banana.storeПреимущества
Клиенту, использующему Banana.store.BananaStore, доступны только те функции, которые мы хотим сделать доступными. Версия гибернации - это деталь реализации, о которой им не нужно знать, и они не должны видеть эти классы, поскольку они добавляют беспорядок к операциям хранения.
Прочие логические и функциональные преимущества
Чем дальше к корню, тем шире становится область действия, и вещи, принадлежащие одному пакету, начинают проявлять все больше и больше зависимостей от вещей, принадлежащих другим модулям. Если бы кто-то исследовал, например, «банановый» модуль, большинство зависимостей было бы ограничено внутри этого модуля. Фактически, на большинство помощников в разделе «банан» вообще не будет ссылок за пределами этого пакета.
Почему функциональность?
Какой ценности можно достичь, объединяя вещи по функциональности. Большинство классов в таком случае независимы друг от друга, и необходимость в использовании частных методов или классов пакета практически отсутствует. Реорганизация их в собственные пакеты мало что дает, но помогает уменьшить беспорядок.
Разработчик вносит изменения в систему
Когда разработчикам поручают внести изменения, которые являются немного более чем тривиальными, кажется глупым, что потенциально у них есть изменения, которые включают файлы из всех областей дерева пакетов. При логически структурированном подходе их изменения более локальны в той же части дерева пакетов, что кажется правильным.
источник
И функциональный (архитектурный), и логический (функциональный) подходы к упаковке имеют место. Многие примеры приложений (которые можно найти в учебниках и т. Д.) Следуют функциональному подходу, заключающемуся в размещении презентаций, бизнес-сервисов, сопоставления данных и других архитектурных уровней в отдельных пакетах. В примерах приложений каждый пакет часто имеет только несколько или только один класс.
Этот первоначальный подход хорош, поскольку надуманный пример часто служит для: 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.
источник
Это зависит от степени детализации ваших логических процессов?
Если они автономны, у вас часто есть новый проект для них в системе управления версиями, а не новый пакет.
Проект, над которым я сейчас работаю, ошибается в сторону логического разделения, есть пакет для аспекта jython, пакет для механизма правил, пакеты для foo, bar, binglewozzle и т. Д. Я ищу специальные синтаксические анализаторы XML / Writers для каждого модуля в этом пакете, вместо того, чтобы иметь пакет XML (что я делал ранее), хотя по-прежнему будет базовый пакет XML, в котором идет общая логика. Однако одна из причин этого заключается в том, что он может быть расширяемым (плагины), и, следовательно, каждый плагин также должен будет определять свой код XML (или базы данных и т. Д.), Поэтому централизация этого может вызвать проблемы позже.
В конце концов, это кажется наиболее разумным для конкретного проекта. Однако я думаю, что это легко упаковать в соответствии с типичной многоуровневой диаграммой проекта. В итоге вы получите сочетание логической и функциональной упаковки.
Что нужно, так это тегированные пространства имен. Синтаксический анализатор XML для некоторых функций Jython может быть помечен как Jython, так и XML, вместо того, чтобы выбирать один или другой.
Или, может, я болтаю.
источник
Я стараюсь проектировать структуры пакетов таким образом, чтобы, если бы я нарисовал граф зависимостей, было бы легко следовать и использовать последовательный шаблон с как можно меньшим количеством циклических ссылок.
Для меня это намного проще поддерживать и визуализировать в вертикальной системе именования, а не в горизонтальной. если component1.display имеет ссылку на component2.dataaccess, это вызывает больше предупреждений, чем если бы display.component1 имел ссылку на dataaccess. компонент2.
Конечно, компоненты, общие для обоих, идут в собственном пакете.
источник
Я полностью слежу и предлагаю логичную («побочную») организацию! Пакет должен как можно точнее следовать концепции «модуля». Функциональная организация может распространять модуль по проекту, что приводит к меньшей инкапсуляции и подверженности изменениям в деталях реализации.
Возьмем, к примеру, плагин Eclipse: размещение всех представлений или действий в одном пакете было бы беспорядком. Вместо этого каждый компонент функции должен перейти в пакет функции или, если их много, в подпакеты (обработчики featureA., настройки featureA. и т. Д.)
Конечно, проблема заключается в иерархической системе пакетов (которая, среди прочего, есть в Java), что делает обработку ортогональных проблем невозможной или, по крайней мере, очень сложной - хотя они встречаются везде!
источник
Я лично выбрал бы функциональное именование. Краткая причина: это позволяет избежать дублирования кода или кошмара зависимостей.
Позвольте мне немного уточнить. Что происходит, когда вы используете внешний файл jar с собственным деревом пакетов? Вы эффективно импортируете (скомпилированный) код в свой проект, а вместе с ним (функционально разделенное) дерево пакетов. Имеет ли смысл использовать два соглашения об именах одновременно? Нет, если это не было скрыто от вас. И это так, если ваш проект достаточно мал и состоит из одного компонента. Но если у вас несколько логических модулей, вы, вероятно, не захотите повторно реализовывать, скажем, модуль загрузки файла данных. Вы хотите разделить его между логическими модулями, не иметь искусственных зависимостей между логически несвязанными модулями, и вам не нужно выбирать, в какой модуль вы собираетесь поместить этот конкретный общий инструмент.
Я предполагаю, что именно поэтому функциональное именование чаще всего используется в проектах, которые достигают или предназначены для достижения определенного размера, а логическое именование используется в соглашениях об именах классов, чтобы отслеживать конкретную роль, если какой-либо из каждого класса в пакет.
Постараюсь точнее ответить на каждый из ваших пунктов по логическому именованию.
Если вам нужно ловить рыбу в старых классах, чтобы изменить функциональные возможности при изменении планов, это признак плохой абстракции: вы должны создавать классы, которые обеспечивают четко определенные функциональные возможности, определяемые одним коротким предложением. Только несколько классов высшего уровня должны собрать все это, чтобы отразить ваш бизнес-интеллект. Таким образом, вы сможете повторно использовать больше кода, упростить обслуживание, получить более четкую документацию и меньше проблем с зависимостями.
Это в основном зависит от того, как вы разрабатываете свой проект. Безусловно, логический и функциональный вид ортогональны. Поэтому, если вы используете одно соглашение об именах, вам нужно применить другое к именам классов, чтобы сохранить некоторый порядок, или перейти от одного соглашения об именах к другому на некоторой глубине.
Модификаторы доступа - хороший способ разрешить другим классам, которые понимают вашу обработку, получить доступ к внутренностям вашего класса. Логические отношения не означают понимания алгоритмических ограничений или ограничений параллелизма. Функционально может, хотя его нет. Я очень устал от модификаторов доступа, отличных от публичных и частных, потому что они часто скрывают отсутствие правильной архитектуры и абстракции классов.
В больших коммерческих проектах смена технологий происходит чаще, чем вы думаете. Например, мне пришлось уже 3 раза менять XML-парсер, 2 раза технологию кеширования и 2 раза программное обеспечение геолокации. Хорошо, что я спрятал все мелкие детали в специальном пакете ...
источник
utils
пакета. Тогда любые классы, которым это нужно, могут просто зависеть от этого пакета.Интересный эксперимент - вообще не использовать пакеты (кроме корневого пакета).
Тогда возникает вопрос, когда и почему имеет смысл вводить пакеты. Предположительно, ответ будет отличаться от того, что вы ответили бы в начале проекта.
Я предполагаю, что ваш вопрос вообще возникает, потому что пакеты похожи на категории, и иногда трудно выбрать одно или другое. Иногда теги были бы более полезны, чтобы сообщить, что класс можно использовать во многих контекстах.
источник
С чисто практической точки зрения конструкции видимости java позволяют классам в одном пакете получать доступ к методам и свойствам с помощью
protected
иdefault
видимостью, а также кpublic
тем. Использование непубличных методов из совершенно другого уровня кода определенно будет большим запахом кода. Поэтому я стараюсь помещать классы из одного слоя в один и тот же пакет.Я не часто использую эти защищенные методы или методы по умолчанию где-либо еще - за исключением, возможно, модульных тестов для класса, - но когда я использую, они всегда из класса на том же уровне
источник
Это зависит. В моей работе мы иногда разделяем пакеты по функциям (доступ к данным, аналитика) или по классам активов (кредит, акции, процентные ставки). Просто выберите структуру, наиболее удобную для вашей команды.
источник
По моему опыту, повторное использование создает больше проблем, чем решает. С новейшими и дешевыми процессорами и памятью я бы предпочел дублирование кода, а не тесную интеграцию для повторного использования.
источник