Это не часто задаваемый вопрос о разнице между api и implementation, и, надеюсь, он будет более продвинутым и интересным с точки зрения архитектуры многомодульного проекта.
Допустим, у меня есть следующие модули в приложении
librarybasefeature1feature2appТеперь отношения между модулями:
base обертывает library
feature1 и feature2 используют (зависит) от base
app объединяет feature1 и feature2
Все в этой многомодульной структуре должно иметь возможность работать с зависимостями Gradle implementation, и нигде не нужно использовать предложение api.
Теперь предположим, что feature1 должен получить доступ к деталям реализации base, включенным в library.
Насколько я могу судить, чтобы сделать library доступным для feature1, у нас есть два варианта:
Измените implementation на api в base, чтобы передать зависимость модулям, которые зависят от base.
Добавьте library в качестве зависимости implementation к feature1 без утечки зависимости base от library.
Конечно, пример упростили ради вопроса, но вы понимаете, как это может превратиться в конфигурационный ад с большим количеством модулей с 4 или 5 уровнями зависимостей.
Мы могли бы создать промежуточный модуль base-feature, который может обернуть base и предоставить feature1 другой уровень абстракции для использования без утечки library, но давайте оставим это решение за рамками этой проблемы, чтобы сосредоточиться на настройке зависимостей.
Некоторые компромиссы, который я обнаружил в приведенных выше параметрах:
Вариант 1) плюсы
build.gradle, так как нет необходимости повторять пункты implementation.api и посмотрите, как изменения распространяются на все потребительские модули.Вариант 1) минусы
Вариант 2) плюсы
Вариант 2) минусы
implementation должны быть изменены. Несмотря на то, что я считаю, что это хорошая вещь, потому что она точно отслеживает, как изменение изменило проект, я понимаю, что это может занять больше времени.Теперь вопросы:
Есть ли какие-либо компромиссы с точки зрения компиляции этого многомодульного сценария?
Является ли модуль, пропускающий зависимость, «быстрее» для компиляции потребительских модулей?
Сильно ли это влияет на время сборки?
Какие еще побочные эффекты, плюсы/минусы мне не хватает?
Спасибо за ваше время.
Репост из темы Форум Gradle.
То, что вы описываете, является довольно распространенным обсуждением систем многоуровневой архитектуры, также известных как «строгие» и «свободные» слои или «открытые» и «закрытые» слои. См. эту (надеюсь, бесплатную) главу из Шаблоны архитектуры программного обеспечения для некоторых семиотических вопросов, которые вряд ли сильно помогут вам с вашим выбором.
С моей точки зрения, если модуль должен разбить слои, я бы смоделировал структуру проекта, чтобы показать это наиболее прямым и наглядным образом. В данном случае это означает добавление library в качестве зависимости реализации от feature1. Да, это делает диаграмму более уродливой, да, это заставляет вас трогать еще несколько файлов при обновлении, и в этом суть - в вашем дизайне есть недостаток, и теперь он виден.
Если несколько модулей должны разорвать инкапсуляцию уровня таким же образом, я могу рассмотреть возможность добавления отдельного базового модуля, раскрывающего эту функциональность, с таким именем, как base-xyz. Добавление нового модуля — это большое дело не из-за технической работы, а потому, что наш мозг может обрабатывать только определенное количество «вещей» за раз (фрагментирование). Я считаю, что то же самое будет справедливо и для «вариантов» Gradle, когда они станут доступны, но я пока не могу этого утверждать, поскольку я не пробовал их на практике.
Если всем клиентам модуля base требуется доступ к library (т. е. потому что вы используете классы или исключения из library в своих общедоступных подписях), вам следует предоставить library как зависимость API от base. Недостатком этого является то, что library становится частью общедоступного API base, и он, вероятно, больше, чем вам хотелось бы, и не находится под вашим контролем. Публичный API — это то, за что вы несете ответственность, и вы хотите, чтобы он был небольшим, документированным и обратно совместимым.
В этот момент вы можете думать о модулях jigsaw (хорошо), osgi (эээ… не стоит) или об обертывании частей lib, которые вам нужно выставить в ваших собственных классах (может быть?)
Обертывание только ради разрыва зависимостей — не всегда хорошая идея. Во-первых, это увеличивает объем кода, который вы поддерживаете и (надеюсь) документируете. Если вы начнете делать небольшие адаптации на уровне base, а library является хорошо известной библиотекой, вы вносите несоответствия (с добавленной стоимостью) - нужно всегда быть начеку, остаются ли в силе их предположения для lib. Наконец, часто тонкие оболочки в конечном итоге приводят к утечке дизайна библиотеки, поэтому, даже если они обертывают API, это все равно заставляет вас касаться клиентского кода при замене/обновлении lib, и в этот момент вам, возможно, лучше использовать lib напрямую.
Итак, как видите, речь идет о компромиссах и удобстве использования. Процессору все равно, где лежат границы вашего модуля, и все разработчики разные - кто-то лучше справляется с большим количеством простых вещей, кто-то лучше справляется с небольшим количеством высоко абстрактных понятий.
Не зацикливайтесь на лучшем (как в «Что бы сделал дядя Боб») дизайне, когда сработает любой хороший дизайн. Количество дополнительной сложности, которое оправдано для введения порядка, является нечеткой величиной, и это то, что вы должны решить. Лучше позвони и не бойся завтра поменять :-)
Извините, но я не понимаю вашего вопроса. Какую часть моего ответа вы имеете в виду? Что вы подразумеваете под форматированием кода: база, обертки, библиотека, реализация? Небольшое цитирование и контекст помогут, хотя я понимаю, что это сложно сделать в комментарии SO. Хотите перенести обсуждение в связанную ветку на форуме Gradle?
Просто пройдя несколько лет спустя, «платформы» Gradle — это хороший способ связать версии библиотек, которые всегда используются вместе. Однажды я могу обновить ответ выше, но пока обязательно прочитайте документы :-)
О какой утечке идет речь? Если я использую
implementationв библиотеках, то в хост-приложении я буду вынужден писать что-то вродеimplementation library implementation wraps implementation base, иначе приложение будет падать во время выполнения. Т.е. утечкаwrapsиbaseвсе равно будет. Если я используюapiв библиотеках, то в хост-приложении буду вынужден писать что-то типаimplementation libraryи все. и здесь тоже будет ваша "утечка"