# Composition
One of the most compelling arguments for object-oriented programming is code reuse.
You may first think of “reuse” as “copying code.” Copying seems like an easy solution, but it doesn’t work very well. As time passes, your needs evolve. Applying changes to code that’s been copied is a maintenance nightmare. Did you find all the copies? Did you make the changes the same way for each copy? Reused code can be changed in just one place.
In object-oriented programming you reuse code by creating new classes, but instead of creating them from scratch, you use existing classes that someone has already built and debugged. The trick is to use the classes without soiling the existing code.
Inheritance is one way to achieve this. Inheritance creates a new class as a type of an existing class. You add code to the form of the existing class without modifying the original. Inheritance is a cornerstone of object-oriented programming.
You can also choose a more straightforward approach, by creating objects of existing classes inside your new class. This is called composition, because the new class is composed of objects of existing classes. You’re reusing the functionality of the code, not its form.
Composition is used frequently in this book. Composition is often overlooked because it seems so simple—you just put an object inside a class.
Composition is a has-a relationship. “A house is a building and has a kitchen” can be expressed like this:
// Composition/House1.kt
package composition1
interface Building
interface Kitchen
interface House: Building {
val kitchen: Kitchen
}
Inheritance describes an is-a relationship, and it’s often helpful to read the description aloud: “A house is a building.” That sounds right, doesn’t it? When the is-a relationship makes sense, inheritance usually makes sense.
If your house has two kitchens, composition yields an easy solution:
// Composition/House2.kt
package composition2
interface Building
interface Kitchen
interface House: Building {
val kitchen1: Kitchen
val kitchen2: Kitchen
}
To allow any number of kitchens, use composition with a collection:
// Composition/House3.kt
package composition3
interface Building
interface Kitchen
interface House: Building {
val kitchens: List<Kitchen>
}
We spend time and effort understanding inheritance because it’s more complex, and that complexity might give the impression that it’s somehow more important. On the contrary:
Prefer composition to inheritance.
Composition produces simpler designs and implementations. This doesn’t mean you should avoid inheritance. It’s just that we tend to get bound up in more complicated relationships. The maxim prefer composition to inheritance is a reminder to step back, look at your design, and wonder whether you can simplify it with composition. The ultimate goal is to properly apply your tools and produce a good design.
Composition appears trivial, but is powerful. When a class grows and becomes responsible for different unrelated things, composition helps pull them apart. Use composition to simplify the complicated logic of a class.
# Choosing Between Composition and Inheritance
Both composition and inheritance put subobjects inside your new class—composition has explicit subobjects while inheritance has implicit subjobjects. When do you choose one over the other?
Composition provides the functionality of an existing class, but not its interface. You embed an object to use its features in your new class, but the user sees the interface you’ve defined for that new class rather than the interface of the embedded object. To hide the object completely, embed it privately:
// Composition/Embedding.kt
package composition
class Features {
fun f1() = "feature1"
fun f2() = "feature2"
}
class Form {
private val features = Features()
fun operation1() =
features.f2() + features.f1()
fun operation2() =
features.f1() + features.f2()
}
The Features
class provides implementations for the operations of Form
, but the client programmer who uses Form
has no access to features
—indeed, the user is effectively unaware of how Form
is implemented. This means that if you find a better way to implement Form
, you can remove features
and change to the new approach without any impact on code that calls Form
.
If Form
inherited Features
, the client programmer could expect to upcast Form
to Features
. The inheritance relationship is then part of Form
—the connection is explicit. If you change this, you’ll break code that relies upon that connection.
Sometimes it makes sense to allow the class user to directly access the composition of your new class; that is, to make the member objects public. This is relatively safe, assuming the member objects use appropriate implementation hiding. For some systems, this approach can make the interface easier to understand. Consider a Car
:
// Composition/Car.kt
package composition
import atomictest.*
class Engine {
fun start() = trace("Engine start")
fun stop() = trace("Engine stop")
}
class Wheel {
fun inflate(psi: Int) =
trace("Wheel inflate($psi)")
}
class Window(val side: String) {
fun rollUp() =
trace("$side Window roll up")
fun rollDown() =
trace("$side Window roll down")
}
class Door(val side: String) {
val window = Window(side)
fun open() = trace("$side Door open")
fun close() = trace("$side Door close")
}
class Car {
val engine = Engine()
val wheel = List(4) { Wheel() }
// Two door:
val leftDoor = Door("left")
val rightDoor = Door("right")
}
fun main() {
val car = Car()
car.leftDoor.open()
car.rightDoor.window.rollUp()
car.wheel[0].inflate(72)
car.engine.start()
trace eq """
left Door open
right Window roll up
Wheel inflate(72)
Engine start
"""
}
The composition of a Car
is part of the analysis of the problem, and not simply part of the underlying implementation. This assists the client programmer’s understanding of how to use the class and requires less code complexity for the creator of the class.
When you inherit, you create a custom version of an existing class. This takes a general-purpose class and specializes it for a particular need. In this example, it would make no sense to compose a Car
using an object of a Vehicle
class—a Car
doesn’t contain a Vehicle
, it is a Vehicle
. The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition.
The cleverness of polymorphism can make it can seem that everything ought to be inherited. This will burden your designs. In fact, if you choose inheritance first when you’re using an existing class to build a new class, things can become needlessly complicated. A better approach is to try composition first, especially when it’s not obvious which approach works best.
Exercises and solutions can be found at www.AtomicKotlin.com.