вступление
В сегодняшней статье мы продолжим статью на прошлой неделе о создании Kotlin-подобных компоновщиков в Java и Python , расширяя API-интерфейсы компоновщика для принятия некоторых из необязательных параметров для большей гибкости. Мы продолжаем с нашим примером HTML, пытаясь добавить атрибуты тега, такие как class, id и style.
Котлин и Питон
Kotlin настраивает использование этих параметров именно так, как я бы это делал в Python: аргументы по умолчанию и именованные аргументы. Использование Kotlin будет выглядеть примерно так:
1
2
3
4
5
6
7
|
html { body { p(klass= "myClass" , id= "theParagraph" ) { + "the paragraph text" } } } |
Обратите внимание на использование «класса» вместо «класса». Классическое столкновение ключевых слов и идентификаторов. Вы можете использовать «cls», «clazz» или что угодно, если хотите. Я бы предложил отойти от всего, что обычно используется в языке для объектов класса, так как это вообще другой класс.
Это довольно большое обновление тега p
с прошлой недели (которое было просто p = "text"
), изменяющее его свойство на полноценный метод. Но большинству других примеров не потребуется столько работы. Вот обновленный код Kotlin:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
class Body { ... fun p( class : String= "" , id: String= "" , style: Style=Style.blank, paragraphBuilder: Paragraph.() -> Unit) { val p = Paragraph( class , id, style) paragraphs.add(p) p.paragraphBuilder() } ... } class Paragraph(val class : String, val id: String, val style: Style) { var text: String = "" operator fun plus(other: String) { text += other } } |
Обновленный код Python (все еще использующий первую версию) будет выглядеть так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Body: def __init__(self): self.paragraphs = ... def p(self, klass= '' , id= '' , style=None): par = Paragraph(klass, id, style) self.paragraphs.append(par) return par class Paragraph: def __init__(self, klass, id, style): self.klass = klass self.id = id self.style = style self.text = '' def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): return False def __iadd__(self, text): self.text += text |
__iadd__()
— оператор сложения на месте, позволяющий нам сказать p += 'text'
. В Kotlin мы использовали +
вместо +=
потому что нам не нужно ссылаться на объект абзаца, поэтому неправильно начинать с +=
, тогда как нам нужно ссылаться на p
в коде Python, поэтому +=
выглядит более естественным, сделав так, чтобы мы могли изменить код вызова на что-то вроде этого:
1
2
3
4
5
|
html = Html() with html as html: with html.body() as body: with body.p( class = 'myClass' , id= 'theParagraph' ) as p: p += 'the paragraph text' |
И Kotlin, и Python принимают объект Style
вместо того, чтобы просто принимать другую строку, как остальные. На самом деле, я бы порекомендовал сделать то же самое для class и id, так как тогда мы передаем объекты класса и id с их настройками CSS, которые также используются с компоновщиком CSS. Я только не делал это здесь ради примеров. Я не позволил Style
оставаться строкой, потому что для лучшей ясности и правильности лучше использовать его с каким-то конструктором стилей CSS
Ява
И Kotlin, и Python делают переход довольно простым. К сожалению, Java не имеет необходимого набора функций, чтобы позволить такое легкое изменение; Вы должны полагаться на старые беглые API трюки, чтобы пройти через это.
Перегрузка в изобилии!
Первая мысль состоит в том, чтобы добиться максимально возможного эквивалента преобразования с кучей перегрузок. Вы создаете быстрые и удобные обертки строк для полей класса и id, так как они являются просто строками, что затрудняет их различение в противном случае:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class Class { public final String text; public Class(String text) { this .text = text; } } class ID { public final String text; public ID(String text) { this .text = text; } } |
Что делает всю перегрузку похожей на это:
01
02
03
04
05
06
07
08
09
10
|
class Body { ... public void p(Consumer<Paragraph> paragraphBuilder) {...} public void p(Class klass, Consumer...) {...} public void p(ID id, Consumer...) {...} public void p(Style style, Consumer...) {...} public void p(Class klass, ID id, Consumer...) {...} // and so on... 3 more times ... } |
Это становится настолько утомительным, что я даже не закончил писать каждую строку, не говоря уже о начале всех строк. И это только принимает во внимание класс, идентификатор и стиль; Есть еще. Идти по этому маршруту просто бесполезно. Следовательно, я даже не покажу, как выглядит полученный код. Кроме того, что касается остальных идей, я не собираюсь показывать реализацию API, надеясь, что он достаточно очевиден. Если вам действительно интересно, как реализовать один из API, дайте мне знать.
Внутренняя настройка
Другой способ установить эти атрибуты — сделать это в компоновщике. Дайте методы Paragraph
для установки этих значений. Внутри тела тег будет выглядеть примерно так:
1
2
3
4
5
|
html.body(body -> { body.p(p -> { p.klass = "myClass" ; p.id = "theParagraph" ; p.addText( "the paragraph text" ); }); }); |
Это не страшно (особенно если эти строки установщика находятся на первой строке; размещение их в последующих строках может привести к путанице в их назначении), и это, вероятно, самое простое, но вероятность плохого кода немного высока:
1
2
3
4
5
6
7
|
html.body(body -> { body.p(p -> { p.klass = "myClass" ; p.addText( "the paragraph text" ); p.id = "theParagraph" ; }); }); |
Давайте посмотрим на некоторые другие варианты.
Атрибут Объекты
Только с двумя перегрузками функции p()
(одна, которая принимает только функцию построителя, а другая — как объект Attributes
), мы можем создать довольно чистый API, который будет выглядеть примерно так:
1
2
3
4
5
|
html.body(body -> { body.p(Attributes.klass( "myClass" ).id( "theParagraph" ), p -> { p.addText( "the paragraph text" ); }); }); |
Лично это мой любимый. Требуется больше уроков и больше реальной сложности, но я чувствую, что это самый расширяемый. Самое большое неудобство в том, что разные HTML-теги имеют разные наборы атрибутов. Вероятно, должен существовать общий класс конструктора Attributes
, а также класс, специфичный для тега, который увеличивает количество перегрузок до 4 (без атрибутов, только базовые, только для тегов и обоих типов). Четыре перегрузки допустимы, но, вероятно, не должно быть. Если это кажется слишком большим, вероятно, лучше придерживаться последней стратегии.
Для полноты картины у меня есть еще один, который на самом деле может работать лучше для других API, которые не имитируют HTML или XML.
Пост-Call Building
Эта последняя идея состоит в том, чтобы Body.p()
возвращал Paragraph
(предпочтительно, второй этап компоновщика, поскольку в противном случае эти методы были бы доступны в Body.p()
-компоновщике) для вызова методов, например:
1
2
3
4
5
|
html.body(body -> { body.p(p -> { p.addText( "the paragraph text" ); }).klass( "myClass" ).id( "theParagraph" ); }); |
Это существенно перемещает классы Attributes
до конца, как второй этап построителя Paragraph
.
Outro
Это лучшее, что я могу тебе дать. Если вы заинтересованы в создании свободно распространяемых API на таких языках, как Java, вам следует ознакомиться со статьей jOOQ о том, как они это делают . Это другой способ, который даже не принимает во внимание лямбды, и это прекрасно. В любом случае, я поговорю с вами, ребята, на следующей неделе, когда я начну серию коротких рецензий на книги.
Ссылка: | Kotlin-подобные конструкторы в Java и Python, продолжение: дополнительные параметры от нашего партнера по JCG Джейкоба Циммермана в блоге « Идеи программирования с Джейком» . |