Core Java

Kotlin-like Builders in Java and Python, Continued: Additional Parameters

Intro

In today’s article, we follow up last week’s article about making Kotlin-like builders in Java and Python, expanding the builder APIs to take some of the optional parameters for more flexibility. We continue on with our HTML example, trying to add tag attributes, such as class, id, and style.

Kotlin and Python

The way Kotlin sets up the use of those parameters is exactly how I would do it in Python: default arguments and named arguments. Using Kotlin would look something like this:

html {
   body {
      p(klass="myClass", id="theParagraph") {
         + "the paragraph text"
      }
   }
}

Note the use of “klass” instead of “class”. Classic clash of keywords and identifiers. You can use “cls”, “clazz”, or whatever, if you want. I would suggest moving away from whatever is normally used in the language for class objects, since this is a different kind of class altogether.

This is a pretty big upgrade to the p tag from last week (which was just p = "text"), changing it from a property to a full-blown method. But most of the other examples won’t need quite as much work. Here’s the updated Kotlin code:

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
   }
}

The updated Python code (still using the first version) would look like this:

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__() is the in-place addition operator, allowing us to say p += 'text'. In Kotlin, we used + instead of += because we don’t have to reference the paragraph object, so it looks wrong to start with +=, whereas we need to reference p in the Python code, so += looks more natural, making it so we can change the calling code to something more like this:

html = Html()
with html as html:
    with html.body() as body:
        with body.p(class='myClass', id='theParagraph') as p:
            p += 'the paragraph text'

Both Kotlin and Python take a Style object instead of just accepting another string, like the others. In actuality, I would recommend doing the same for class and id as well, since then we pass in class and id objects with their CSS settings that are also used with a CSS builder. I only didn’t do it here for the sake of examples. I didn’t let Style remain a string because it would be better served with some sort of CSS style builder for better clarity and correctness.

Java

Both Kotlin and Python make the transition pretty simple. Unfortunately, Java doesn’t have the required feature set to allow such an easy change; you have to rely on old fluent API tricks to get you through it.

Overloads Galore!

The first thought is to go for as equivalent of a conversion as possible with a bunch of overloads. You make quick, convenient string wrappers for the class and id fields, since they’re both just strings, making it difficult to distinguish between the two otherwise:

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;
   }
}

Which makes all the overload look something like this:

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
   ...
}

This gets so tedious that I didn’t even finish writing each line, let alone start all the lines. And this only takes into account class, id, and style; there are more. Going this route is just no good. Hence, I won’t even show what the resulting code looks like. Also, for the rest of the ideas, I’m not going to bother showing the implementation of the API, hoping that it’s self-evident enough. If you’re really curious about how to implement one of the APIs, let me know.

Internal Setting

Another way to set those attributes is to do so within the builder. Give the Paragraph methods for setting those values. Inside the body tag would look something like this:

html.body(body -> {
   body.p(p -> { p.klass = "myClass"; p.id = "theParagraph";
      p.addText("the paragraph text");
   });
});

This isn’t terrible (especially with those setter lines being up on the first line; putting them on follow-up lines would confuse their purpose), and it’s probably the simplest, but the potential for bad code is a bit high:

html.body(body -> {
   body.p(p -> {
      p.klass = "myClass";
      p.addText("the paragraph text");
      p.id = "theParagraph";
   });
});

Let’s look at some other options.

Attribute Objects

With only two overloads of p() (one that accepts just the builder function, and one that accepts that as well as an Attributes object), we can make a pretty clean API that looks more like this:

html.body(body -> {
   body.p(Attributes.klass("myClass").id("theParagraph"), p -> {
      p.addText("the paragraph text");
   });
});

Personally, this is my favorite. It takes more classes and more real complexity, but I feel like it’s the most expandable. The biggest inconvenience is that different HTML tags have different sets of attributes. There should probably be a common Attributes builder class, plus one that is tag-specific, bringing the number of overloads up to 4 (no attributes, just the basic ones, just the tag-specific ones, and both kinds). Four overloads is tolerable, but probably shouldn’t be. If it seems like too much, it’s probably better to stick with the last strategy.

For the sake of completeness, though, I have one more, which may actually work better for other APIs that aren’t mimicking HTML or XML.

Post-Call Building

This last idea is to have Body.p() return the Paragraph (preferably a second stage of builder, since these methods would be available within the builder lambda otherwise) to call the methods on, like this:

html.body(body -> {
   body.p(p -> {
      p.addText("the paragraph text");
   }).klass("myClass").id("theParagraph");
});

This essentially moves the Attributes classes to the end, as a second stage of the Paragraph builder.

Outro

That’s the best I can give you. If you’re interested in building fluent APIs in languages like Java, you should check out jOOQ’s article about how they do it. It’s a different way that doesn’t even take lambdas into account, which is just fine. Anyway, I’ll talk to you guys next week, when I kick off a short series of book review articles.

Jacob Zimmerman

Jacob is a certified Java programmer (level 1) and Python enthusiast. He loves to solve large problems with programming and considers himself pretty good at design.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button