Scala has popularized the "Pimp my library" pattern:
This is just a fancy expression to refer to the ability to supplement a library using implicit conversions.
Kotlin does provide the same capability. However, it achieves it via extension functions. While the generated bytecode is similar to Java's static methods, the developer experience is the same as adding functions to existing types.
This approach has limitations, though. One cannot update the type hierarchy.
Imagine a library that offers a component with an open/close lifecycle. The component opens when you instantiate it, but you need to make sure to close it after usage, e.g., a file, a stream, etc.
Before Java 7, you had actually to close the component explicitly:
Component component; try { component = new Component(); // Use component } finally { component.close(); }
Java 7 introduced the try-with-resource statement so that you can write something like this:
try (Component component = new Component()) { // Use component } // 1
Component
is closed here in a generatedfinally
block
However, Component
must implement AutoCloseable
.
Kotlin provides the use()
extension function on Closeable
.
Hence, one can replace the try-with-resource statement with a simple function call:
Component().use { // Use component as it } // 1
Component
is closed here in afinally
block
That being said, imagine that the library above implements neither Closeable
nor AutoCloseable
. We cannot use use()
. Kotlin delegation to the rescue!
The Delegation pattern is widespread in Object-Oriented languages:
In delegation, an object handles a request by delegating to a second object (the delegate). The delegate is a helper object, but with the original context. With language-level support for delegation, this is done implicitly by having
self
in the delegate refer to the original (sending) object, not the delegate (receiving object). In the delegate pattern, this is instead accomplished by explicitly passing the original object to the delegate, as an argument to a method.-- Wikipedia
Implementing the Delegation pattern in Java requires writing a lot of boilerplate code. The more methods the original class has, the more boring it is:
interface class Component { void a(); void b(); void c(); } public class CloseableComponent extends Component implements Closeable { private final Component component; public CloseableComponent(Component component) { this.component = component; } void a() { component.a(); } void b() { component.b(); } void c() { component.c(); } public void close() {} }
Kotlin supports the Delegation pattern out-of-the-box via the by
keyword. One can rewrite the above code as:
interface Component { fun a() {} fun b() {} fun c() {} } class CloseableComponent(component: Component) : Component by component, Closeable { // 1 override fun close() {} }
- Delegate all calls of
a()
,b()
, andc()
to the underlyingcomponent
We can finally write the desired code:
CloseableComponent(RealComponent()).use { // Use component as it }
Even better, it works with third-party code to improve an external library with this approach.
The icing on the cake, one can also call the code from a try-with-resource Java statement:
try (CloseableComponent component = new CloseableComponent(new RealComponent())) { // Use component }
As I wrote above, one can do it in Java also. In general, however, the sheer amount of boilerplate code that one needs to write to implement delegation is a significant impediment. Kotlin makes it a breeze.
We miss one last step to make our code easier to write. How do we get the CloseableComponent
? Let's create an extension function on Component
:
fun Component.toCloseable() = CloseableComponent(this)
And now, usage is fluent:
RealComponent().toCloseable().use { // Use component }
In this post, we have seen how to improve the API provided by third-party libraries. We achieved it by combining Kotlin extension functions and delegation.
To go further:
- Extending third-party APIs in different languages
- Extension functions
- The try-with-resources Statement
- Kotlin's use
- Delegation pattern
- Kotlin's Delegation
Originally published at A Java Geek on December 19th, 2021