Composition vs Inheritance: The Kotlin by keyword

This article was originally written on medium and migrated to here.

To make your code more flexible and maintainable, it’s essential to understand composition and inheritance. This article features two simple examples With Android Activities to illustrate these design patterns, and explains the by keyword in Kotlin. Code source available in this GitHub repository.

A vibrant jigsaw puzzle with pieces in the colors of the Kotlin programming language, separated and spread out. The pieces include deep blue, teal, and orange sections, with a glossy finish and varied shapes and sizes. The word ‘Kotlin’ is prominently written in the center. The background is dynamic and colorful, featuring gradient hues and patterns, adding an attractive and lively touch to the overall image.

Understanding Composition and Inheritance

Composition and inheritance are two fundamental design principles in object-oriented programming:

  • Inheritance allows a class to inherit properties and methods from another class, promoting code reuse.
  • Composition involves building classes using other classes, promoting more flexible and modular code structures.

Let’s delve into examples to see how these principles are applied in Kotlin.

Example with Inheritance

In the inheritance-based approach, we have a MainActivityInheritance class that extends a BaseActivity. The BaseActivity contains shared functionality that is inherited by MainActivityInheritance.

class MainActivityInheritance : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startView()
}
}

open class BaseActivity : AppCompatActivity() {
fun startView() {
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startView()
}
}

Pros of Inheritance:

  • Simple and straightforward.
  • Encourages code reuse through shared base class functionality.

Cons of Inheritance:

  • Tight coupling between the base class and derived class.
  • BaseActivity tells nothing about what this class do. We’ll need to open this class to understand better.
  • Less flexibility and harder to modify behavior without affecting all derived classes.

Example with Composition

In the composition-based approach, we use Kotlin’s by keyword to delegate the implementation of an interface to another class. The MainActivityComposition class implements the ViewStarter interface by delegating to ViewStarterImpl.

class MainActivityComposition : AppCompatActivity(), ViewStarter by ViewStarterImpl {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
startView(findViewById(R.id.main))
}
}

interface ViewStarter {
fun startView(view: View)
}
object ViewStarterImpl : ViewStarter {
override fun startView(view: View) {
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
}

Pros of Composition:

  • Greater flexibility and modularity, we can just create another activity and implement the same interface and the same ViewStarterImpl.
  • ViewStarter interface make clear for us that this is starting a view. Is more readable.
  • Looser coupling between classes.
  • Easier to modify or extend functionality.

Cons of Composition:

  • Slightly more complex to set up and understand initially.
  • A little more boilerplate code to delegate behaviors.

Why Choose Composition?

The choice between composition and inheritance largely depends on your specific use case. However, composition often provides greater flexibility and promotes a more modular architecture, especially in complex applications.

Understanding the Kotlin “by” Keyword

The by keyword, especially in the context of ViewStarter by ViewStarterImpl, can be a bit tricky at first. To make it easier, let’s imagine I’m the MainActivityComposition object saying:

I will implement the ViewStarter interface, but I don’t want to override the interface methods! Instead, I’ll ask another object, called ViewStarterImpl, to implement it for me.

As The activity says, Using composition, MainActivityComposition can access all methods from the ViewStarter interface without worrying about the implementation details. This delegation allows for cleaner and more maintainable code.

Conclusion

In this article, we’ve explored the differences between composition and inheritance in Kotlin using practical examples, using the by keyword.

Source code here.

$date =

;

author =

;

Previous =

;

Discover more from Gabriel Machado - Software Engineer

Subscribe now to keep reading and get access to the full archive.

Continue reading