Статьи

Подразумеваемая читаемость

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

обзор

Этот пост основан на разделе статьи, которую я недавно написал для InfoQ . Если вы заинтересованы в прохождении Jigsaw, вы должны прочитать весь текст.

Все не приписанные цитаты взяты из превосходной системы состояния модуля .

Определение (подразумеваемой) читабельности

Зависимость модуля от другого модуля может принимать две формы.

Резюме: удобочитаемость

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

Возьмем, к примеру, Guava , где код, зависящий от модуля, совершенно не заботится о том, использует ли он внутренние неизменяемые списки или нет.

подразумеваемая читаемость-требует

Это наиболее распространенный случай, и он охватывается концепцией читабельности :

Когда один модуль напрямую зависит от другого […], тогда код в первом модуле сможет ссылаться на типы во втором модуле. Поэтому мы говорим, что первый модуль читает второй или, что то же самое, что второй модуль читается первым.

Здесь модуль может получить доступ к API другого модуля, только если он объявил о своей зависимости от него. Таким образом, если модуль зависит от Guava, другие модули остаются в неведении об этом и не будут иметь доступа к Guava без объявления своих собственных явных зависимостей от него.

Подразумеваемая читаемость

Но есть еще один вариант использования, когда зависимость не полностью инкапсулирована, а живет на границе между модулями. В этом сценарии один модуль зависит от другого и предоставляет типы из зависимого модуля в своем собственном общедоступном API.

В примере с Guava открытые методы модуля могут ожидать или возвращать неизменяемые списки.

подразумеваемая читаемость-требует, общественности

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

Вот где подразумевается читабельность :

[Мы] расширяем объявления модулей, так что один модуль может предоставить читаемость дополнительным модулям, от которых он зависит, любому модулю, который зависит от него. Такая подразумеваемая читаемость выражается включением общедоступного модификатора в предложение require.

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

Примеры

От JDK

Давайте посмотрим на модуль java.sql . Он предоставляет интерфейс Driver , который возвращает Logger через открытый метод getParentLogger() . java.logging принадлежит java.logging . Из-за этого java.sql публично требует java.logging , поэтому любой модуль, использующий функции SQL Java, также может получить доступ к API журналирования.

Поэтому дескриптор модуля java.sql может выглядеть следующим образом:

1
2
3
4
5
module java.sql {
    requires public java.logging;
    requires java.xml;
    // exports ...
}

Из календаря Jigsaw Advent

Календарь содержит модуль advent.calendar , в котором содержится список из 24 сюрпризов, по одному на каждый день. Сюрпризы являются частью advent.surprise module . Пока это выглядит как открытый и закрытый случай для регулярного предложения require.

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

1
2
3
4
module org.codefx.demo.advent.calendar {
    requires public org.codefx.demo.advent.surprise;
    // exports ...
}

подразумеваемая читаемость

Опубликовано Питером Хоппером под CC-BY-NC 2.0

За пределами модуля

Система состояния модуля рекомендует, когда использовать подразумеваемую читабельность:

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

Но как далеко мы должны взять это?

Оглядываясь на пример java.sql , должен ли модуль, использующий его, также require java.logging ? Технически такая декларация не нужна и может показаться излишней.

Чтобы ответить на этот вопрос, мы должны посмотреть, как именно наш фиктивный модуль использует java.logging. Может потребоваться только прочитать его, чтобы мы могли вызвать Driver.getParentLogger() , изменить уровень журнала в Driver.getParentLogger() и покончить с этим. В этом случае взаимодействие нашего кода с java.logging происходит в непосредственной близости от его взаимодействия с Driver from java.sql . Выше мы назвали это границей между двумя модулями.

В качестве альтернативы наш модуль может фактически использовать ведение журнала в своем собственном коде. Затем типы из java.logging появляются во многих местах независимо от Driver и больше не могут рассматриваться как ограниченные границей нашего модуля и java.sql .

Аналогичное сопоставление может быть создано для нашего календаря advent advent.calendar ли основной модуль advent , для которого требуется advent.calendar , только advent.surprise для фабрик-сюрпризов, необходимых для создания календаря? Или у него есть модуль сюрприза независимо от его взаимодействия с календарем?

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

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

Агрегация и разложение

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

Агрегаторные модули объединяют функциональность связанных модулей в единое целое.

Одним из методов является создание так называемых агрегаторных модулей , которые не содержат кода самостоятельно, но агрегируют ряд других API для более удобного использования. Это уже используется Jigsaw JDK, который моделирует компактные профили как модули, которые просто представляют те самые модули, пакеты которых являются частью профиля.

Другое — то, что Алекс Бакли называет разложимостью вниз : модуль можно разложить на более специализированные модули без последствий для совместимости, если он превратится в агрегатор для новых модулей.

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

Модули агрегатора несут особую ответственность: объединять функциональность связанных модулей в единое целое. Изменение содержимого пакета является ключевым изменением. «Обычная» подразумеваемая читаемость, с другой стороны, часто будет проявляться между не непосредственно связанными модулями (как с java.sql и java.logging ), где подразумеваемый модуль используется более случайно.

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

отражение

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

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

Ссылка: Подразумеваемая читаемость от нашего партнера JCG Николая Парлога в блоге CodeFx .