Composition over inheritance in Kotlin way

Composition over inheritance is an important principle in Object-oriented programming. Instead of implementing all the functionalities of desired interfaces inside one monolithic class, these functionalities should be implemented independently in various instances, and then use these objects to eventually empower the target class with all the provided functionalities. This principle make the code more reusable and maintainable.
The Example
Let me explain the idea in a simple example. Imagine you are working in a automobile company, and your job is to manufacture cars based on various requirements like the color of the car (appearance), the max speed (performance), and the number of seats (interior). Of course, there are tons of other car characteristics that a modern car can have, let us just keep these three properites in this example.
The Java Way
To implement it in Java, we first declare three interfaces, Appearance
, Performance
and Interior
.
public interface Appearance {
String getColor();
}public interface Performance {
int getMaxSpeed();
}public interface Interior {
int getNumberOfSeats();
}
Now your boss asks you to manufacture a yellow slow SUV, to do it with Inheritance is no-brainer.
public class YellowSlowSUV implements
Appearance,
Performance,
Interior {
@Override
public String getColor() {
return "yellow";
}
@Override
public int getNumberOfSeats() {
return 6;
}
@Override
public int getMaxSpeed() {
return 160;
}
}
The preceding code looks OK if this is the only car that you are going to build, but what are you going to do if you are asked to build another car that has everything the same, except changing the color to red?
You might start with copy and paste the code from YellowSlowSUV
class to the new class RedSlowSUV
and then change the return value of getColor()
to red? Well, it is straightforward, but not a scalable solution. What happens if you are asked to build another GreenSlowSUV
or even BlackSlowSUV
? Repeating the whole copy and paste process again with a different returned color value is definitely a sign of code smell, and we are not going down this route.
Let us see how Composition can applied in this case. Instead of implementing the three interfaces in one class, we can have three concrete functionality classes that implement these interfaces respectively.
// Red.java
public class Red implements Appearance {
@Override
public String getColor() {
return "Red";
}
}// Slow.java
public class Slow implements Performance {
@Override
public int getMaxSpeed() {
return 160;
}
}// SixSeat.java
public class SixSeat implements Interior {
@Override
public int getNumberOfSeats() {
return 6;
}
}
And the RedSlowSUV
can be implemented as follows.
public class RedSlowSUV implements Appearance, Performance, Interior {
private Appearance appearance = new Red();
private Interior interior = new SixSeat();
private Performance performance = new Slow();
@Override
public String getColor() {
return appearance.getColor();
}
@Override
public int getNumberOfSeats() {
return interior.getNumberOfSeats();
}
@Override
public int getMaxSpeed() {
return performance.getMaxSpeed();
}
}
The actual implementations of functionalities are provided by Red
, SixSeat
and Slow
classes, so we are able to reuse these classes in other Car
implementations based on the requirements. Therefore, we can easily build a car, i.e., aGreenSlowSUV,
by leveraging the existing Slow
and SixSeat
classes, with just an additional Green
class.
public class GreenSlowSUV implements Appearance, Performance, Interior {
private Appearance appearance = new Green();
private Interior interior = new SixSeat();
private Performance performance = new Slow();
@Override
public String getColor() {
return appearance.getColor();
}
@Override
public int getNumberOfSeats() {
return interior.getNumberOfSeats();
}
@Override
public int getMaxSpeed() {
return performance.getMaxSpeed();
}
}
In this way, we are able to build new Cars by leveraging the hard works that we already put into building the functionality blocks, and create classes to handle new business requirements if they cannot be met with existing blocks, eventually these new blocks can also be reused.
You may notice the boilerplate code (getColor(),
getNumberOfSeats()
and getMaxSpeed()
) that we have in each Car
class. We can of course create a base classes that implement all these methods. But this is so Java and it is 2018 already :) Let use see how we can improve it with Kotlin.
The Kotlin Way
There are two important Kotlin keywords we need to understand before proceed further, object
and by
. The object
keyword instructs the Kotlin complier to create exact one instance of the declared class. And for by
keyword, we see it mostly used in the lazy loading examples together with thelazy
function. It is less common (at least to me) to know that we can also use by
keyword to delegate the implementation of an interface to another object. Let us see some codes in action.
First, we declares functionality classes as object
and implement them.
object Red: Appearance {
override fun getColor(): String {
return "Red"
}
}
object SixSeat: Interior {
override fun getNumberOfSeats(): Int {
return 6
}
}
object Slow: Performance {
override fun getMaxSpeed(): Int {
return 160;
}
}
And then we can have our Car
class in just one line of code :)
class RedSlowSUV: Appearance by Red,
Interior by SixSeat, Performance by Slow
Since the required implementations of the interfaces are delegated by the objects via the by
keyword, we do not need to explicitly declares those methods that are already delegated by objects, because the Kotlin compiler is intelligent enough to generate required code for us under the hood. Let us fire up the Kotlin byte code viewer and see them.
/// Decompiled code shown in Kotlin byte code viewer
public final class RedSlowSUV implements Appearance, Interior, Performance {
// $FF: synthetic field
private final Red $$delegate_0;
// $FF: synthetic field
private final SixSeat $$delegate_1;
// $FF: synthetic field
private final Slow $$delegate_2;
public RedSlowSUV() {
this.$$delegate_0 = Red.INSTANCE;
this.$$delegate_1 = SixSeat.INSTANCE;
this.$$delegate_2 = Slow.INSTANCE;
}
public String getColor() {
return this.$$delegate_0.getColor();
}
public int getNumberOfSeats() {
return this.$$delegate_1.getNumberOfSeats();
}
public int getMaxSpeed() {
return this.$$delegate_2.getMaxSpeed();
}
}
As shown above, the generated Java byte code looks very similar to what we have written in the Java implementation example earlier. So basically Kotlin helps write all these boilerplate code for us.
Summary
In this article, we have recapped the principle of Composition over Inheritance with an simple example in Java implementation. We have also seen the Kotlin way of achieving the same goal in a more expressive and concise way without all the boilerplate code. Lastly we examined the generated Java byte code produced by Kotlin compiler and understood the actual heavy work that Kotlin did underneath.
It is just the tip of the iceberg of what Kotlin can benefit Java / Android developers, there are many more things that we can get from Kotlin to help us write better codes, and I will introduce them in the future articles.
Stop boilerplating and start coding :)