Kotlin DSL | Coding a DSL: 3 — The ‘Triangle’ and ‘Rhombus’ objects

Glenn Sandoval
Kotlin and Kotlin for Android
5 min readJul 5, 2021

Building and adding ‘Triangle’ and ‘Rhombus’ objects

You can go to any article of this series by clicking on one of the links below:

Kotlin DSL

  1. Introduction
  2. Base knowledge to build a DSL in Kotlin — Part 1
  3. Base knowledge to build a DSL in Kotlin — Part 2
  4. Codebase: Project Shapes-DSL
  5. Coding a DSL: 1 — Package structure and the ‘Panel’ object
  6. Coding a DSL: 2 — The ‘Square’ object
  7. Coding a DSL: 3 — The ‘Triangle’ and ‘Rhombus’ objects
  8. Coding a DSL: 4 — The ‘Empty Space’ and the ‘Composed Shape’ object
  9. Coding a DSL: 5 — plus and minus operators and inline functions
  10. Coding a DSL: 6 — The @DslMarker annotation
  11. Experimenting and conclusions

In the previous article, we defined the square function which creates and adds Square shapes to the Panel through the SquareBuilder class. At the end of the previous article, the main method’s content looked like this:

main.kt — Step 3

Building the ‘Triangle’ object

Repeating what we did for the Square shape in the previous article, we are going to change the Triangle instantiation and its addition to the Panel by replacing the following instructions…

…to make them look like this:

square and triangle functions are practically identical so let’s move forward a little bit faster using what we already have learned. We can assume that triangle is an extension function of the Panel class, just like square function. Instead of a Square object, it should return a Triangle object. Its signature should look as follows:

fun Panel.triangle(?): Triangle

💬 Again, I temporarily replace the lambda function type with a question mark since we don’t know it yet.

Since the triangle function follows the same pattern as the square function, we can assume that it applies the builder design pattern to create Triangle objects, so let’s create a new class for it.

Create a new Kotlin class named TriangleBuilder inside console_shapes_dsl.builders package. Its code should look like this:

💬 Don’t worry about duplicated code, we are going to abstract their common behavior to a parent class at the end of this article.

The lambda function type that we are looking for is analogous to the lambda function type of the square function. Let’s define it like this: TriangleBuilder.() -> Unit.

Finally, the signature of the triangle function looks as follows:

fun Panel.triangle(init: TriangleBuilder.() -> Unit): Triangle

Go to the console_shapes_dsl.external package and add the following code to the Extension.kt file:

fun Panel.triangle(init: TriangleBuilder.() -> Unit): Triangle {

}

We know that this function has to do the following:
— Create a Triangle object through a TriangleBuilder.
— Add the new Triangle object to the Panel.
— Return the new Triangle object.

Its code should look like this:

fun Panel.triangle(init: TriangleBuilder.() -> Unit): Triangle {
val builder = TriangleBuilder()
builder.init()
val triangle = builder.build()
this.addShape(triangle)
return triangle
}

Finally, we can reduce all its code to a single line with scope functions like apply and also:

▶️ If you run the code, you’ll see that it works again.

So far, the main method’s content looks like this:

main.kt — Step 4

Building the ‘Rhombus’ object

Now that we have created both functions square and triangle, we can create the rhombus function the same way.

Our goal now is to replace the following instructions…

…to make them look like this:

As we already have learned, we will need an intermediary object to create Rhombus objects, so let’s create its class.

Create a new Kotlin class named RhombusBuilder inside console_shapes_dsl.builders package. Its code should look like this:

Now let’s create the rhombus function taking square and triangle functions as a guide. We can tell that rhombus function is an extension function of the Panel class and, at the same time, it is a high-order function whose lambda function type corresponds to RhombusBuilder.() -> Unit. It also has to return a Rhombus object. Its signature should be as follows:

fun Panel.rhombus(init: RhombusBuilder.() -> Unit): Rhombus

Go to the console_shapes_dsl.external package and add the following code to the Extension.kt file:

fun Panel.rhombus(init: RhombusBuilder.() -> Unit): Rhombus {

}

Just like square and triangle functions, rhombus function has to do the following:
— Create a Rhombus object through a RhombusBuilder.
— Add the new Rhombus object to the Panel.
— Return the new Rhombus object.

Its code should look like this:

fun Panel.rhombus(init: RhombusBuilder.() -> Unit): Rhombus {
val builder = RhombusBuilder()
builder.init()
val rhombus = builder.build()
this.addShape(rhombus)
return rhombus
}

▶️ If you run the code, you’ll see that it works again.

And just like we did before, let’s reduce its code to a single line with the scope functions apply and also:

Go to the main method and make sure everything works. Its code should look like this:

main.kt — Step 5

We could leave all 3 builders just like that, however, as a good practice we should abstract its common behavior to a parent class.

Abstracting builders’ common behavior

As a good practice we should define a constant that stores the default character to be used in case their property char is not set during their construction.

We can summarize their common code to the following:
— Definition of their property lines set to 0.
— Definition of their property char set to the default character.
— Definition of their member function build that returns a Shape object.

Go to the console_shapes_dsl.builders package and create a new abstract Kotlin class named ShapeBuilder. Its implementation should look like the following code:

We mark its member function build as abstract so every subclass must define its own behavior.

Now go to the SquareBuilder class and make it extend ShapeBuilder class. Then remove its lines and char properties. Finally, modify its member function build as follows:

override fun build() = Square(lines, char)

SquareBuilder class should look like this:

💬 Make sure everything works before modifying the other 2 builders.

Now let’s modify TriangleBuilder and RhombusBuilder classes the same way:

TriangleBuilder:

RhombusBuilder:

We have reached the end of this article. Now we only have space and composed functions definition left. These functions add “empty spaces” and composed shapes to the Panel respectively. We are going to create them in the next article and we will apply other Kotlin features to create a more idiomatic DSL.

💬 If you enjoyed this article, you can show your appreciation by buying me a coffee at the link below. Thanks for reading and for your support.

--

--

Glenn Sandoval
Kotlin and Kotlin for Android

I’m a software developer who loves learning and making new things all the time. I especially like mobile technology.