Kotlin has Interface, Abstract Classes and Delegation, while Python has multiple inheritance. Different tools, same problems. The different tools reflect an evolution of what Object Oriented best summed up by: Composition over inheritance. The rethink, the new concepts and how to use them are explained here.
- Abstract Classes
- Interfaces: Duck Typing with Safety
- Multiple Interfaces vs Multiple Inheritance
- Composition over Inheritance
- Dependency Injection
- Kotlin Class Delegates: Automated Delegation
- Summary
Abstract Classes.
With Koltin, Interfaces can do almost all that abstract classes can do, and the reverse is not true. So why learn two things instead of one, when only one you need is interfaces? Answer: Don’t bother learning abstract classes until experienced with every other topic here. For more this answer to ‘why use abstract classes vs interfaces‘ from stackoverflow is useful in three ways:
- it highlights that it is not obvious why use and abstract class when in almost all cases an interface is preferred
- the answers on stack overflow are all related to rather unusual cases where even with Kotlin, there some benefit from the use of an abstract class over an interface
- it makes the point that interfaces in Kotlin are more powerful than in other languages, making abstract classes largely unnecessary
Consider the above points. Don’t bother with abstract classes unless coverting java code that has interfaces. In languages such a java, the less powerful interfaces create more use for abstract classes, but they are mostly outdated by more powerful interfaces.
Interfaces: Better Duck Typing
Polymorphism is one of the four basic pillars of Object Oriented Programming, and inheritance is a key tool to deliver polymorphism. Interfaces provide an alternative way to deliver polymorphism, and are more in keeping with duck typing.
Inheritance is based on the idea that the new class is a type of the old class. While an interface lists what the two classes can both do. The Wikipedia pages a Is-a and Has-a discuss the concepts at considerable length, and provide a lot of further reading if desired.
Consider the example started in the polymorphism section and continued in the inheritance section with Animals objects. The Animals then subclassed to Duck objects and Dog objects, with Ducks then again subclassed to specific Duck types. In the example, the base Class Animal has ‘talk()
‘ and ‘move()
‘ methods, and a ‘size
‘ property. ‘Animal'
is a common choice as example for inheritance, this example can also be coded using an interface. We can even leave the methods of the interface empty, as in reality, the base Class ‘Animal
‘ cannot really have a meaningful value for ‘size
‘ or meaningful methods of move and talk that are useful for all Animals, or in this case, for both dog and duck. With an interface, there is the choice with every attribute to provide a default, or to leave the implementation empty, which forces code using the interface to provide an implementation.
Now a Duck is an Animal, so passes the ‘Is A’ test. But we can still choose to take the interface approach, and simply say a duck has all of ‘talk()
‘, ‘move()'
and ‘size()
‘. By thinking this way, and only declaring that an object need have these attributes whether on not actually an animal, it allows any object that can have these attributes to effectively be an animal for the purposes of our code. If I have robot vacuum that can move, makes sound (talk) and has size, I can manipulate it with code designed to work with our animal class.
So instead of a Base class, define an interface, that any animal must implement talk, move and have size. If it can talk like an Animal, and move like an Animal, then we can consider it an Animal.
interface Animal{ var size:Int fun move(speed: Int) //functions can have code if desired fun talk(howLoud: Int) } interface StillAnimal: Animal{ override fun move(speed: Int) { } // declare code as empty code... no response to move }
If I declare a Class implements the Animal interface, then I will immediately get an error if I forget to add one of the attributes specified by the interface. The fact is that the code doesn’t care if the an object is actually an animal, as long as the object implements size, move and talk. If fact, this the central premise of Duck Typing in python, that if an object has the attributes required by a block of code, the actual Type doesn’t matter, from the perspective of that block of code, the object is needed type. If the object is asked to do something, and the object responds the right way, then it doesn’t matter what the actual type is for the object.
The challenge with python, is that the only way to see if the object does have the needed attributes, and these attributes are as required (like the base class or interface) and thus they accept the correct parameters etc, is to perform a a significant number tests, or more typically, simply ‘try'
, and handle errors with an ‘except'
if necessary.
In Kotlin, Duck Typing becomes be far easier. Just declare that a parameter with an ‘interface'
as the, in this case the Animal interface, and then any object of any type that implements the animal interface will be accepted as a parameter. Then, no tests are required at run time, as the needed attributes are known to be present and correct. This is the goal of Duck Typing, made simpler and cleaner by Kotlin.
Multiple Interfaces vs Multiple Inheritance
Consider that a that some code requires animals that have specific set of additional attributes. If animal objects are 3D objects within a game, perhaps part of the system wants Animals with expressions. In Python, we could have an Expressions class which could have smile() and frown() methods, the we can make objects that inherit from both Animal and Expressions. In Kotlin, Expressions would be an interface so we could have an object which inherits from Animal and implements the Expression interface, or implements both Animal and Expression interfaces, but cannot inherit two parent objects. Why not? I mean, as people, we inherit from two parents, don’t we? However both ‘Parents’ are Humans, subclassed to Male and Female, where both sub classes have many attributes in common. The reality is while we inherit from two parents, there is a lot of overlap between these two classes, and we may want to access a little of each from those overlaps. So Object Inheritance does not deliver the model of inheritance in nature. This same problem of duplicate attributes, gets more complex with objects inheriting from multiple parent objects. Inheritance implementations require a strategy to ensure only one of each potential duplicate is inherited, but typically this means giving priority to one parent. No fathers eyes, and mothers smile… there is one priority for every duplicated attribute. The fact that this does not always work is specifically recognises with the Python init() which will try to run the init() of both parents with super(), and it gets complex. Articles on multiple inheritance in Python get into the MRO and how to handle the duplications. Replacing multiple inheritance of Classes, with multiple inheritance of interfaces allows easier eliminating sources of duplication at the outset, as in interface has only the elements specifically designed to be inherited, no constructors or methods not specifically nominated to be in the interface. But moving to interfaces does not solve everything. What if you still want not just an interface, but an object the implements the interface of each parent?
Composition over inheritance.
Composition, rather than inheritance is seen as the ideal solution when a complete implementations of multiple objects is required. Composition means the new object will contain an instance of both ‘parent’ object, rather than inherit from both parents. The object ‘composed’ is then in full control to select attributes from either or both parents.
Consider a case where we desire the behaviour of a Duck object (where a Duck is a Class that inherits from Animal) and also require the behaviour an Expression object.
Inheritance: With Python, the data to instance our desired Duck and desired Expression can be passed to the our new object, which inherits from both Classes. In the init() method of our new class, the data to init each of the two parents has been passed in the constructor, so we have the data to init each parent.
Basic Composition(worst case): We could, rather than call init() for each parent, not use inheritance at all but use the data we would pass to init() the create a new object each type.
#inheritance class ExpressiveDuck(Duck, Expression): def __init__(self, duck_data, express_data): # note super()__init__() does not work, if parent init has data Duck.__init__(self, duck_data) Expression.__init__(self, express_data) #attempt at composition class ExpressiveDuck: def __init__(self, duck_data, express_data): self.duck = Duck(duck_data) self.express = Expression(express_data) @property def size(self): return self.duck.size def move(self, speed): return self.duck.move(speed) def smile(self, howHappy): return self.express.smile(howHappy)
The example for composition actually needs even two more methods, but they were omitted just to save space. It is already clear that in this case, with python, composition requires significantly more code. Despite all the questions and agonising, even the init() method for the inheritance example can be clear if you avoid super() , at least for parents where the init() has parameters…. and init methods will nearly always need parameters in the real world.
Init() is the only complexity in this inheritance example, because it is the only attribute that is in both parents. No duplicated attributes makes this a example a best case for inheritance, and a worst case example for composition with python. However, as we will see, there are still reasons to have composition, and in Kotlin, even this example no longer need be considered a ‘worst case’.
Dependency Injection.
In the previous Composition over Inheritance example, Duck and Expression become the dependencies for the class ExpressiveDuck. Whether using Inheritance or Composition, ExpressiveDuck depends on Duck and Expression classes. Now here is an example in Kotlin just to demonstrate the move to allow dependency injection, while still allowing compatibility with that previous example.
// note... this is the long way to do this! class ExpressiveDuck(var duck:Duck, var expression: Expression) : Animal, Expression // announce both interfaces will be delivered! // note 'Duck' is an implementation of the Animal Interface { constructor(duckData: DuckData, expressData: ExpressData) :this( Duck(duckData), Expression(expressData) // now delegate the attributes as needed override val size get() = duck.data override fun move(speed) = duck.move(speed) override fun talk(howLoud) = duck.talk(howLoud) override fun smile(howHappy) = express.smile(howHappy) override fun frown(howMad) = express.frown(howMad) }
This new ExpressiveDuck has a primary constructor that accepts the two objects that it ‘inherits’ from. This means there is now a constructor that could work for any object that implements the Duck and Expression interfaces. No need to create a whole family of Classes for different parent Classes, the parent class can now be supplied as a parameter. In fact, we no longer need an ExpressiveDuck class at all, and the same code will work as ExpressiveAnimal. Since the Duck class is a specific implementation (sub subclass) of Animal, with the same interface as Animal, our class will make an expressive ‘any animal we like as long as you pass one of those animals as a parameter’.
Further, the generic nature of having the parent class as a parameter means we could be passed a special subclass that simply adds checking whenever move is called, of completely mocks the Expression object to allow better unit tests.
If only it wasn’t for those pesky delegations(too much boilerplate to type)!
Note: As many examples, this one is somewhat contrived. Duck would probably be a class that implements the Animal interface, so if we went back to the original idea of just a ExpressiveDuck, then we could inherit from Duck and the above example using only using the above features would be simpler. Ok, but not as flexible, and it turns out we can make this solution even better.
Kotlin Class Delegates: Automated Delegation.
In fact Kotlin does address those ‘pesky delegations’. Called ‘Class Delegates’, instead of just listing the interfaces a class is declaring will implemented, it can also delegate the implementation to an object, just as we did manually in the previous example.
// note... now the Kotlin way!! class ExpressiveDuck(var animal:Animal, var expression: Expression) : Animal by animal, Expression by expression { // delegated! constructor(duckData: DuckData, expressData: ExpressData) :this( Duck(duckData), Expression(expressData) // all delegates are automatic!
So now the code is as concise as the Python inheritance example, even for this worst case for using construction. Not only is the code as concise as the Python code, it is far more functional, still supporting the same specific case with the same interface (when called with duckData and expressData) but removing the need for a whole family of classes by working with Animal or any Expression implementation, and even supporting mocking for testing.
Note, explicit overrides are still required where an attribute is duplicated within the delegate interfaces. However the IDE will alert the developer, rather than needing to detect unexpected behaviour at run time.
Summary
The combination of Interfaces and Class Delegates allows for an improved solution to multiple inheritance, that can provide dependency injection with no additional overhead.
[…] which are all key OOP tools, are supported at language level with Kotlin. As the examples on the Interfaces and Delegation page show, such techniques as dependency injection and ‘composition over inheritance’ […]
LikeLike