вступление
В сегодняшней статье мы продолжим статью на прошлой неделе о создании 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 parclass 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 Джейкоба Циммермана в блоге « Идеи программирования с Джейком» . |