Declarative and Immutable Pipeline of Transformations

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Несколько месяцев назад я создал небольшую библиотеку на Java, которую стоит объяснить, так как дизайн ее классов и интерфейсов довольно необычен. Она очень объектно-ориентирована для довольно императивной задачи: создания конвейера преобразования документов. Цель состояла в том, чтобы делать это декларативным и неизменяемым способом, и на Java. Ну, насколько это возможно.

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

Сначала я создал интерфейс Shift (вместо часто используемого и скучного “преобразования”):

Затем я создал интерфейс Train (это имя я придумал для совокупности преобразований) и его реализацию по умолчанию.

Ах, я забыл сказать тебе. Я большой поклонник неизменяемых объектов. Вот почему у Train нет метода add, а вместо него есть with. Разница в том, что add изменяет объект, а with создает новый.

Теперь я могу создать поезд с перегрузками с помощью TrDefault, простой реализации Train, предполагая, что ShiftA и ShiftB уже реализованы.

Затем я создал класс Xsline (это “XSL” + “pipeline”, поскольку в моем случае я управляю XML-документами и преобразую их с помощью таблиц стилей XSL). Экземпляр этого класса инкапсулирует экземпляр Train и затем проходит документ через все его преобразования:

So far so good.

Теперь я хочу, чтобы все мои преобразования записывались в журнал. Я создал StLogged, декоратор Shift, который инкапсулирует исходный Shift, декорирует его метод apply и выводит сообщение в консоль, когда преобразование завершено.

Сейчас мне нужно сделать это:

Похоже на дублирование new StLogged(, особенно с коллекцией из нескольких десятков сдвигов. Чтобы избавиться от этого дублирования, я создал декоратор для Train, который динамически декорирует сдвиги, которые он инкапсулирует, используя StLogged.

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

Есть явное дублирование new StXSL(...), но я не могу просто избавиться от него, так как метод with ожидает экземпляр Shift, а не String. Чтобы решить эту проблему, я сделал Train обобщенным и создал декоратор TrClasspath:

TrClasspath.with() accepts String, turns it into StXSL and passes to TrDefault.with().

Обратите внимание на приведенный выше фрагмент: train теперь имеет тип Train<String>, а не Train<Shift>, как требуется Xsline. Вопрос теперь в том, как вернуться к Train<Shift>?

Ах, я забыл упомянуть. Я хотел разработать эту библиотеку с одним важным принципом, предложенным в 2014 году: все объекты могут реализовывать только методы своих интерфейсов. Вот почему я не мог просто добавить метод getEncapsulatedTrain() к TrClasspath.

Я ввел новый интерфейс Train.Temporary<T> с единственным методом back(), возвращающим Train<T>. Класс TrClasspath его реализует, и я могу сделать так:

Затем я решил избавиться от дублирования вызовов .with(). Очевидно, что было бы проще иметь возможность предоставить список имен файлов в виде массива String и создать поезд из него. Я создал новый класс TrBulk, который именно это делает:

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

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

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-05 at 21:40

sixnines availability badge   GitHub stars