LoginSignup
3
0

More than 1 year has passed since last update.

Composition over Inheritance - Delegation in Kotlin

Last updated at Posted at 2022-12-14

Favour Composition over Inheritance

When dealing with code duplication and reusabilty issues it is good to favour Composition over Inheritance.

Overview

  • This is a universally accepted concept.
  • Inheritance is not always bad.
  • Composition is not suitable for or used in all circumstances.
  • Kotlin encourages Composition and makes Inheritance harder.
  • Design and document for Inheritance else prohitibt it.
  • Composition should be the first design choice in mind.
  • We should still use Inheritance if it is required and designed for.

Example 1

Let's look at an example. We add some an some animals to a program.

Animals with duplication
class Dog {
   fun bark()
   fun eat()
}

class Cat {
   fun meow()
   fun eat()
}

We have dupication of the eat() function so let's solve this with a base Animal class.

Animals with Inheritance
class Animal {
   fun eat()
}

class Dog: Animal {
   fun bark()
}

class Cat: Animal {
   fun meow()
}

However, we get the error This type is final, so it cannot be inherited from.

This is because Kotlin helps us not abuse Interitance so by default classes are final which means we need the special keyword open to extend them (Java is the opposite).

Let's add the open keyword and inherit from the base class.

Animals with Inheritance
open class Animal {
   fun eat()
}

class Dog: Animal {
   fun bark()
}

class Cat: Animal {
   fun meow()
}

It looks good so far with Inheritance.

Now let's add some more functionality to the program. We now need to add some robots.

Robots with duplication
class CleaningRobot {
   fun clean()
   fun drive()
}

class FeedingRobot {
   fun feed()
   fun drive()
}

There is duplication on drive() so let's use Inheritance to solve this.

Robots with Inheritance
open class Robot {
   fun drive()
}

class CleaningRobot: Robot {
   fun clean()
}

class FeedingRobot: Robot {
   fun feed()
}

All is well so far but now we add more functionality.

We now require a cleaning robot dog. It needs to drive(), clean() and bark() but doesn't eat().

It doesn't look good for Inheritance because we can only inherit from one class. We could try and make a new super class but you will end up with an unclear base class with too much responsibility.

We are stuck because Inheritance acts as a contract that is hard to break. Also the more features you add the tighter the coupling becomes and the tighter the coulping the more you add features!

This can be avoided by opting for Composition.

Composition

  • It is the concept of a class referencing instances of other objects (every class basically does that)
  • Gives same functionality as Inheritance without the coupling.
  • If Inheritance is a good fit you should use it else use Composition.
  • Kotlin is a great language for Composition.

This is an example of Composition.

Class A contains variable B

Composition
class A { 
   val b = B() 
}

Kotlin favours Composition

  • In Kotlin all classes are final and not open for Inheritance by default.
  • It has the keyword Object. This creates a single instance of a class in the compiler.
  • We can delegate the implementation of an Inerface to another class with the keyword By.

So let's use Composition to implement the cleaning robot dog. We need three Interfaces for this:

Interfaces
interface Cleaner { 
   fun clean() 
}

interface Drivable { 
   fun drive() 
}

interface Barker { 
   fun bark() 
}

Now the Interfaces need implmentation. Let's create three Objects (you can also use classes here).

Object implementions
object CleaningRobot: Cleaner { 
   override fun clean() { }
}

object DrivingRobot: Drivable { 
   override fun drive() { } 
}

object BarkingAnimal: Barker { 
    override fun bark() { } 
}

Now the can create the cleaning robot dog class and use Composition.

The By keyword allows us to implement the Interface and delegate the work to the Objects.

CleaningRobotDog with Delegation
class CleaningRobotDog: 
   Cleaner by CleaningRobot,
   Drivable by DrivingRobot,
   Barker by BarkingAnimal

They can then be used again and again to compose classes that require the behavior. No Inheritance needed.

Using the By keyword is called Delegation. We can use the functionality available from delegating class/object which in this case is drive(), clean() and bark().

Example 2

How about a more realistic example.

We require an analytics logger feature to log print statements for app lifecycle events start and pause. We need to include this behaviour in all our screen classes.

A beginner might use a static class for this but that will contain boilerplate code and coupling that is difficult to test.

So let's use Composition to implement the AnalyticsLogger:

AnalyticsLogger
interface AnalyticsLogger { 
   fun registerLifeCycleOwner(owner: LifecycleOwner) 
}

class AnalyticsLoggerImplementation: AnalyticsLogger, LifecycleEventObserver {
   override fun registerLifeCycleOwner(owner: LifecycleOwner) {
      owner.lifecycle.addObserver(this)
   }

   override fun onStateChanged(source: LifecycleOwner, event: Lifecyle.Event) {
      when(event) {
         Lifecycle.Event.ON_RESUME -> println("User opened the screen")
         Lifecycle.Event.ON_PAUSE -> println("User closed the screen")
         else -> Unit
      }
   }
}

We can now add the AnalyticsLogger to our Activity class (screen class).

We just need to pass the LifecycleOwner inside of registerLifeCycleOwner.

The By keyword is then used delegate the work to the AnalyticsLoggerImplementation.

Also because we use an Interface we can add more than one (unlike class Inheritance).

Activity class
class MainActivity: ComposeActivity, AnalyticsLogger by AnalyticsLoggerImplementation {
   override fun onCreate(saveInstanceState: Bundle?) {
      super.onCreate(saveInstanceState)
      registerLifeCycleOwner(this)
   }
}

I hope you learned something new today. Merry Christmas.

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0