A quick update on constructors
In Part II we discussed constructors and saw how to intialize properties based on constructor parameters, a somewhat typical scenario. Kotlin M2 (milestone 2) now supports declaration of properties directly via the constructor. What this means is that we can shorten the following code
This provides us with the properties as before but saves a few extra lines of redundant code. Another change is that constructors can now have optional parameters, meaning that a class only ever requires a single constructor, so there is no constructor overload.
final by default
The base class for a class in Kotlin is Any, a class that does not have any methods or properties. In Kotlin, classes and methods are final (or sealed for C# developers) by default. This is one of the somewhat controversial decisions of Kotlin, and still is not 100% final. The reason for this however is because it is somewhat accepted that one has to design for extensibility consciously.
In order to inherit from a class or override a method, we need to explicitly mark them as open.
The base class Person as well as the method canOverride are declared as open which allows us to inherit from Person and override canOverride. In the inherited Employee class we define overridden method as final which means that inherited classes can no longer override this method.
From the inherited class we can refer to the base class using super (highlighted in the image). Also important to note is that constructors are not inherited. To call a constructor on a base class we merely do so after the colon, as shown above.
Traits, not interfaces
Kotlin does not have interfaces, instead it has traits, which can be thought of as interfaces with optional method implementations. Traits are open by default, as opposed to classes, and to avoid major pitfalls of multiple inheritance, they cannot hold state. As such, they cannot have properties [Edited (27/12/2012): Abstract properties ARE allowed in traits. Thanks Dmitry for pointing this out].
For all practical purposes, a trait is like an interface: we declare a series of methods and have a class implement these
Notice how broadcast does not need to be implemented. Another difference as opposed to interfaces is that we need to explicitly indicate the override directive.
A word on abstracts
Kotlin also allows for abstract classes and methods, like many other languages, and much like these, we merely have to prefix the class with the keyword abstract.
First class delegation
Composition over inheritance is a common design practice to follow, as large inheritance chains come with their own series of issues. When composing classes, it is also considered good practice to use dependency injection, so as to allow for multiple implementations. A typical scenario would be
We define a CustomerRepositoryTrait and then inject a specific implementation in our controller. All operations that have to do with the customer repository are now delegated and accessed via the property customerRepo.
Kotlin goes one step further and inherently understands delegation. As such, the previous code could be written like so
We are telling the compiler that the functionality of the trait is delegated to the injected parameter. One benefit is that we no longer have to explicitly create a property to hold a reference to the dependency. This also has an impact on how we use the code, as we no longer need to dereference it via the dependency, but call it directly. Some might think that this could potentially cause an issue when a class has too many delegated responsibilities. What call belongs to what trait? In actual fact, that usually is a sign of a class doing too much and as such breaking SRP. So think of this more as a warning, a smell.
enums are classes
One of the great features of Kotlin is that enums are classes. This can come in very handy when trying to add behavior.
The CustomerState class is an enum type which provides three values: SIGNED_UP, NORMAL and PREFERRED. What it also provides however is a method called formatted that is defined as abstract. We can now leverage this and override the method for each of the enum values.
Note that methods on enums do not necessarily have to be abstract or open.
We can see that inheritance in Kotlin is pretty straightforward. We have abstract classes, open classes and traits, which are open by default. Methods can also be abstract, open and final. There’s also the notion of first class delegation using the by keyword. In the next part we’re going to dive deeper into functions!