Mixins Via Kotlin Delegation
Mixins can be handy in creating more readable, maintainable code.
Join the DZone community and get the full member experience.
Join For FreeThe State of the Inheritance Union
When Java 8 arrived, interfaces could be fitted out with default methods. This freed developers from having to repeat implementations across classes and enabled you to achieve multiple inheritance of behavior. As compelling as this advancement is, this cannot address many common use cases where it would be desirable to have multiple inheritance of state as well.
State inheritance in Java remains a single inheritance affair. Because of that, developers resort to various composition patterns, which ends up being nice in creating an attractive API for the user of your code, but your code itself can become cluttered with delegated methods.
Traditional Approach
As an example, it is a frequent requirement to know who changed what and when. Regard the following Auditable
interface and Audit
class, which provides both behavior and state, implemented here in Kotlin:
interface Auditable : Serializable {
var whoCreated: String
var whoModified: String
var whenCreated: LocalDateTime
var whenModified: LocalDateTime
var timeZone: ZoneId
@PrePersist
fun createAuditInfo()
@PreUpdate
fun updateAuditInfo()
}
class Audit : Auditable {
override var whoCreated: String = "undefined"
override var whoModified: String = "undefined"
override var whenCreated: LocalDateTime = LocalDateTime.now()
override var whenModified: LocalDateTime = LocalDateTime.now()
override var timeZone: ZoneId = ZoneId.systemDefault()
override fun createAuditInfo() {
whenCreated = LocalDateTime.now()
whenModified = LocalDateTime.now()
if (whoCreated.equals("")) {
whoCreated = "undefined"
}
if (whoModified.equals("")) {
this.whoModified = "undefined"
}
this.timeZone = ZoneId.systemDefault()
}
override fun updateAuditInfo() {
this.whenModified = LocalDateTime.now()
}
}
As is obvious from the above classes, behavior alone is not enough. The functions createAuditInfo
and updateAuditInfo
need to update class properties, i.e. state.
You could use this audit class in an obvious way by using the Decorator pattern. To illustrate this, let us set up a reasonable inheritance structure for an imaginary domain of various types of manuscripts:
@MappedSuperclass
@EntityListeners(GeneratedIdPersistenceListener::class)
open class Manuscript : Identifiable,
Versionable by Versioned() {
@get:Id
@get:Column(columnDefinition = "char(36) default 'undefined'")
override var id: String = ""
}
class GeneratedIdPersistenceListener {
/**
* automatic property set before any database persistence
*/
@PreUpdate
@PrePersist
fun setId(identifiableObject: Any) {
if (identifiableObject !is Identifiable) {
throw IllegalArgumentException("Class must implement " + Identifiable::class.java.canonicalName)
}
when {
identifiableObject.id == null || identifiableObject.id.equals("") || identifiableObject.id.equals("undefined") -> {
val uuid = UUID.randomUUID().toString()
identifiableObject.id = uuid
}
}
}
}
And now, let us use the audit features we created earlier so that we can know who might have edited the manuscript and when. We can delegate to our Auditable
implementation for this in Java:
@Entity
public class Thesis extends Manuscript implements Auditable {
private Auditable audit = new Audit();
@NotNull
@Override
public String getWhoCreated() {
return audit.getWhoCreated();
}
@Override
public void setWhoCreated(@NotNull String whoCreated) {
audit.setWhoCreated(whoCreated);
}
// .. and dozens of more lines of code
The approach above does offer reuse of our Auditable
implementation, which is a huge benefit, but it remains error-prone in that each delegating method must be correctly implemented, and it does nothing in terms of a reduction of verbosity.
If we had used Kotlin instead using this approach, the benefits remain modest as each method call requires a delegation. In both Java and Kotlin, we still have all of our business logic jumbled with the cross-cutting concern of auditability.
Framework Annotation Clutter
Numerous frameworks, such as Hibernate and Jackson, provide significant benefits in terms of code quality and function. By using annotations, we have the benefit of a reduction of magic; we know, for instance, how properties are mapped to tables, XML, and so on. Even so, do we want to see all of that, for instance, auditability purposes, in each and every domain class? The thing is that when we start adding more annotations to the classes to support enterprise features such as JPA, things become very cluttered indeed.
// .. more code up here
@get:Column(columnDefinition = "timestamp default current timestamp not null")
var whenModified: LocalDateTime
@get:Column(columnDefinition = "varchar(36)", length = 36, nullable = false, unique = false, updatable = true)
var timeZone: ZoneId
// and yet more code down here
Now, imagine adding RESTful annotations to the above:
@GET
@get:Produces("text/xml")
@get:Column(columnDefinition = "timestamp default current timestamp not null")
var whenModified: LocalDateTime
And then, imagine repeating the process again and again. It is easy to understand that programmers are rightly wary of copy and paste and, therefore, end up putting the above into a superclass.
If you do that, however, you end up requiring your subclasses to support all of the superclass features and not a subset of them. Over time, however, other repeatable state concerns that you want represented in a slices of your domain occur to you. Take the ordinal position, for example, or version, or even name. If you have defined DDL domains for the database columns of these attributes, then, presumably, you would want to benefit from a single source of truth of that information in the object domain as well so that you only have to change things once and have that change propagate across your entire domain.
/**
* Used for when order is important
*/
interface Orderable : Serializable {
@get:Column(columnDefinition = "integer default 0 not null")
var ordinalPosition: Int
}
class Ordered : Orderable {
override var ordinalPosition: Int = 0
}
Composition alone, through associative relationships, can only help us so far. If you do not use the decorator or similar pattern and if you do not include the information in a superclass, you have a less friendly API, and you need to access the information of interest through an intermediate object as in the following example:
// the example in Java
Thesis thesis = new Thesis();
// constructor, initialization here`
// here we have to first get the Auditable instance, then get the information we need
thesis.getAudit().getWhoCreated();
// more of the same
The above examples are intentionally fine-grained; it is, of course, entirely up to you how much state you wish to segregate and how much can be packaged together. By way of example, Orderable and Nameable and Versionable and Countable could all be in a single interface with a complementary default implementation.
But, there is a better way! Kotlin provides us with pseudo multiple state inheritances through delegation. Let us see how to do it next.
Kotlin Delegates as Mixins
Kotlin classes can inherit from multiple interfaces just like you can do with Java. But less well known is the fact that you can provide default implementations of those interfaces — with state! — via delegates.
Our above example of a thesis is pitiful in that the the sheer length of the class threatens to camouflage any information you might want to share with your fellow developers. Let's try again with another type of manuscript. For example, here, we look at a book:
/**
* What is of interest in this class is the fact
* that it inherits state from multiple sources, that is
* inheritance via delegation, a form of mix-in.
*/
@Entity
@NamedQuery(name = Book.FIND_ALL, query = "SELECT b FROM Book b")
class Book : Manuscript(),
FriendlyIdentifiable by FriendlyId(),
Nameable by Named(),
Auditable by Audit(),
Orderable by Ordered() {
var author: String = ""
@get:OneToMany(mappedBy = "book", cascade = arrayOf(CascadeType.PERSIST))
var chapters: MutableList<Chapter> = ArrayList()
//.. hashCode, toString, equals implementations ...
}
At a glance, we see that we have mixed in all necessary reusable state and improved the readability in the process.
So, what happened? Kotlin, just like Java, does not allow multiple inheritances of state but does allow multiple inheritance of interfaces. Unlike Java, however, Kotlin supports the concept of delegation. So, in the above example, Kotlin is saying that the book is Orderable and that the way the book is ordered is not described by the class Book
itself, but it is delegated to the class Ordered
. So by using a combination of an interface along with an implementation class, we effectively have achieved mixin capability. Combined with the peerless interoperability within Java, Kotlin is a great choice for achieving mixin capability in a mixed Java-Kotlin project.
Mixins and JPA
When used in conjunction with JPA, you need to think of an ORM framework to be able to understand how the annotations work. In this context, the annotations @Embeddable
and @Embedded
become superfluous, as the mixed-in classes are compiled in to the class, implementing the mixin interface. For annotations that access data, you need annotate on the getter
. In Kotlin, that is done via @GET:<annotationName>
. Mixin annotations also need to be placed on the interface, not the implementation.
Even Ids Can Be Outsourced to a Mixin
Experienced JPA programmers know that even when they use an @IdClass
to create multiple keys, another option in the standard Java world is to put the Id into an @Embeddable
and then use the @EmbeddedId
in the entity. In both cases, they still must replicate the fields in the actual entity class.
In Kotlin, we can do better. Here, we can outsource the @id to a mixed-in class just like any other mixin.
@Entity
@EntityListeners(GeneratedShortIdPersistenceListener::class)
class CrazyLongWebPage : Identifiable by Identifier() {
var linesPerScrollPane: Int = 0
}
Although an @id field alone is not onerous, we, nonetheless, typically have a universally defined implementation that we want observed across all classes. Note in the above example that other swap-in implementation approaches, such as an EntityListener
, remain unaffected and obviously still work. This gives you a lot of mixin options.
The advantage becomes even more obvious when you start implementing Composite Keys. This, too, is possible via mixins:
interface CompositeIdentifiable : Serializable {
@get:Id
@get:Column(columnDefinition = "char(36) default 'undefined'")
var name: String
@get:Id
@get:Column(columnDefinition = "char(36) default 'A'")
var letterOfAlphabet: String = ""
}
class CompositeId : CompositeIdentifiable{
override var name: String = ""
override var letterOfAlphabet: String = ""
}
@Entity
@IdClass(CompositeIdentifiable::class)
class EncyclopediaVolume : CompositeIdentifiable by CompositeId() {
//... hasCode, toString, equals implementations
}
This works perfectly.
Summary
Mixins can be handy in creating more readable, maintainable code and are not hard to create even in an enterprise setting, if you understand these simple rules.
There are many reasons to outsource functionality and state to a mixin. The main reason is uniformity and reuse of implementation. The greater the complexity, the more important this becomes. Another big win with this approach is the improved readability of your code.
Source code with test cases can be found on BitBucket here. The tests include API sanity checks, as well as JPA and JAXRS smoke tests.
Opinions expressed by DZone contributors are their own.
Comments