We should be careful how we construct and design our classes in OOP world. If we have simple application or we are building MVP project I wouldn't even bother with this topic at all since code should be short and workable. But we will leave that for other blog post. Now let's define what are these two concepts.
Starting with Inheritance
Inheritance is a concept in OOP which one child class(subclass) inherit everything from the parent class(superclass). When we say everything it means, properties and functions of that superclass. The best is to present with the good old example:
open class Animal {
fun eat() {
println("Njam njam")
}
}
class Dog: Animal() {
fun bark() {
println("Woof!")
}
fun run() {
println("Running..")
}
}
class Cat: Animal() {
fun meow() {
println("Meow!")
}
fun jump() {
println("Jumping..")
}
}
fun main() {
val goofy = Dog()
println("Goofy wants to eat ${goofy.eat()}")
val dizzie = Cat()
println("Dizzie wants to eat ${dizzie.eat()}")
}
We have Animal class as a parent class to the Dog and Cat subclasses. And this is great, we inherited eat() method from the Animal class.
Now the requirements in the project has changed and somebody wants a robot dog which needs to clean around the house and another robot that play some music!
Ah great... Okay, let's define the same thing as above using inheritance.
open class Robot {
fun stop() {
println("Stopping")
}
fun move() {
println("Moving")
}
}
class DogRobot : Robot() {
fun bark() {
println("Woof!")
}
fun clean() {
println("Cleaning around the house")
}
}
class MusicRobot: Robot() {
fun playMusic() {
println("LalaLalLa")
}
}
fun main() {
val goofyRobot = DogRobot()
println("Robot is ${goofyRobot.clean()}")
println("Robot is ${goofyRobot.move()}")
val musicRobot = MusicRobot()
println("Robot playing ${musicRobot.playMusic()}")
println("Stopping robot... ${musicRobot.stop()}")
}
We have our design now. Everything looks cool and suddenly, guess what? Requirements changed again.
Client asks for a robot that can bark, play games, play music, cook, clean around the house. 🙈
Composition to the rescue.
Composition is concept which we are constructing object "from the outside" and we don't inherit anything from other classes instead we are composing it. Let's see the example:
class SuperRobot(
private val dogRobot: DogRobot,
private val musicalRobot: MusicRobot
) {
fun playMusic() {
println("Super robot will play some old school music: ${musicalRobot.playMusic()}")
}
fun bark() {
println("Barking around the house ${dogRobot.bark()}")
}
fun clean() {
println("Cleaning the house... ${dogRobot.clean()}")
}
}
fun main() {
val superRobot = SuperRobot(
dogRobot = DogRobot(),
musicalRobot = MusicRobot()
)
println("Playing music: ${superRobot.playMusic()}")
println("Barking: ${superRobot.bark()}")
println("Cleaning: ${superRobot.clean()}")
}
To achieve this in Kotlin with the inheritance concept is not possible since Kotlin does not support multiple inheritance.
The downsides of the inheritance are:
- we can only extend one class
- we are setting contract for the subclasses which should be respected
- we take everything from the parent class
Pros of using Composition over Inheritance:
- composition is more secure because we don't depend on the class implementation at all, only on the external observables.
- with composition we are more flexible, when we inherit, we take everything, but when we are composing then we can choose what we need for constructing.
- unit testing with composition is easier because we already know from which classes some methods are used.
Summary
General rule in the OOP is using composition over inheritance. Use inheritance only when you must and if you need strict contract which subclasses should respect. In any other case strive for composition.
Easier to remember is like this:
- use inheritance when a class "is"
- use composition when class "has"
Happy coding :)
Top comments (0)