Problem overview
A shop ships parcels in three ways, and every way answers the same two questions: what is the option called, and what does it cost for a given weight? The answers differ. You write one parent class, Shipping, for the standard option, and two child classes, Express and Economy, that inherit from it and replace both answers with their own.
| Class | name() | cost(weight) |
|---|---|---|
Shipping | "Standard" | 5 + weight |
Express | "Express" | 10 + 2 * weight |
Economy | "Economy" | 3 + weight / 2, rounded down |
For a parcel of 4 kilograms, Standard costs 9, Express 18 and Economy 5.
Inheritance and overriding in Kotlin
Kotlin classes are closed by default: nobody can inherit from them, and their methods cannot be replaced. Two keywords open the door:
openon the parent class and on each method that children may replace;overrideon the child's version of the method, with exactly the same name, parameters and return type.
A child names its parent in its header: class Express : Shipping(). The parentheses call the parent's constructor, which here takes no arguments.
If you forget open, the compiler refuses to build the code. For a Shipping class declared without open, the child class gives:
error: this type is final, so it cannot be extended.
error: 'name' in 'Shipping' is final and cannot be overridden.
Step by step
- Write
Shipping, markedopen, withname()returning"Standard". The method isopentoo, so children may replace it. - Give it
cost(weight), returning5 + weight. - In
Express, addoverride fun name()returning"Express". - Override
costinExpressto return10 + 2 * weight. Without it, an express parcel would be priced by the parent's formula. - In
Economy, overridename()to return"Economy". - Override
costinEconomyto return3 + weight / 2. Both operands areInt, so the division drops the remainder:7 / 2is3.
The solution in Kotlin
open class Shipping {
open fun name(): String {
return "Standard"
}
open fun cost(weight: Int): Int {
return 5 + weight
}
}
class Express : Shipping() {
override fun name(): String {
return "Express"
}
override fun cost(weight: Int): Int {
return 10 + 2 * weight
}
}
class Economy : Shipping() {
override fun name(): String {
return "Economy"
}
override fun cost(weight: Int): Int {
return 3 + weight / 2
}
}
Each class changes only itself. Writing cost in Express leaves Shipping and Economy untouched.
One call, three answers
The point of overriding is that code can hold a parcel's option in a variable of the parent type Shipping and still get the child's answer. Kotlin runs the version that belongs to the object's real class:
fun main() {
val options: List<Shipping> = listOf(Shipping(), Express(), Economy())
for (o in options) println("${o.name()}: ${o.cost(4)}")
val e: Shipping = Economy()
println(e.cost(7))
println(e.cost(1))
}
This prints:
Standard: 9
Express: 18
Economy: 5
6
3
The loop is the same three words for every option, yet each line comes from a different class. With a weight of 0 only the base prices remain: 5, 10 and 3. Economy with weight 7 costs 3 + 3 = 6, and with weight 1 it costs 3 + 0 = 3, because 1 / 2 is 0.
Why a parent class at all
Without inheritance you would write three unrelated classes and, in every place that uses a parcel, check which one you hold before asking for its price. Each new shipping option would mean editing all those places. With a shared parent, the code that prints a price depends only on what Shipping promises: a name() and a cost(weight). A fourth option is one more child class, and the code that uses the options does not change.
The parent is also a working class on its own. Shipping() is the standard option, and the children are variations of it, which is why the parent holds the plain versions of both methods instead of leaving them empty.
Testing
Try the three classes on a range of weights and compare the columns, Standard first, then Express, then Economy:
0: 5 10 3
1: 6 12 3
2: 7 14 4
7: 12 24 6
10: 15 30 8
1000: 1005 2010 503
Notice that Economy hardly moves at first: weights 0 and 1 both cost 3, and weight 2 costs 4. That comes from integer division and is exactly what the task asks for. Express adds 2 for each kilogram, and Standard adds 1.
Reusing the parent's version
An override does not have to throw the parent's work away. Inside a child, super reaches the parent's version of a method. This lesson's classes do not need it, because Express and Economy use different formulas, but it is handy when a child only adds to the parent's answer. A child that adds an insurance fee to the standard price could be written like this:
class Insured : Shipping() {
override fun name(): String {
return super.name() + " (insured)"
}
override fun cost(weight: Int): Int {
return super.cost(weight) + 2
}
}
Called through a Shipping variable with a weight of 4, it gives Standard (insured): 11: the parent's 9 plus 2. Don't add this class to your answer; it only shows the idea.
Common mistakes
- Missing
open. Without it on the class or on a method, the child cannot extend or override. - Missing
override. A child method with the parent's name but nooverrideis a compile error too. - Forgetting
()after the parent. The header is: Shipping(), not: Shipping. - Dividing the wrong way.
3 + weight / 2halves the weight only. Writing(3 + weight) / 2halves the base price as well and gives other numbers. - Changing the return type or the parameters. An override must match the parent's header exactly. A method named
costthat takes aLongwould be a different method, not a replacement. - Overriding only one method. If
Expressoverridesnamebut notcost, it reports "Express" with the standard price.
