Problem overview
A shop offers three ways to ship a parcel. Shipping is the standard option, and Express and Economy are special kinds of shipping. Each answers the same two questions, name() and cost(weight), in its own way.
Shipping: the name is"Standard"and the cost is5 + weight.Express: the name is"Express"and the cost is10 + 2 * weight.Economy: the name is"Economy"and the cost is3 + weight / 2.
For a 4 kilogram parcel the three options cost 9, 18 and 5. With a weight of 0 only the base prices are left: 5, 10 and 3.
Inheritance and overriding
A class can extend another class. class Express extends Shipping says that an Express is a kind of Shipping: it gets every method of Shipping and may replace some of them. Replacing a method is called overriding. The new method has the same name and the same parameter and return types as the parent's, and Scala requires the keyword override in front of it.
Here, Express and Economy override both methods. If Express left out cost, an express parcel would get the parent's price, 5 + weight.
The useful part is that the version that runs depends on the object, not on the type of the variable that holds it. A Shipping variable can hold an Express, and calling name() on it still runs the express version.
Step by step
- Write
Shipping.nameso that it returns"Standard". - Write
Shipping.costso that it returns5 + weight. - In
Express, writenamewithoverrideand return"Express". - In
Express, overridecostand return10 + 2 * weight. - In
Economy, overridenameand return"Economy". - In
Economy, overridecostand return3 + weight / 2.
Each class changes only itself. Overriding in Express has no effect on Shipping or Economy.
The solution in Scala
class Shipping {
def name(): String = {
return "Standard"
}
def cost(weight: Int): Int = {
return 5 + weight
}
}
class Express extends Shipping {
override def name(): String = {
return "Express"
}
override def cost(weight: Int): Int = {
return 10 + 2 * weight
}
}
class Economy extends Shipping {
override def name(): String = {
return "Economy"
}
override def cost(weight: Int): Int = {
return 3 + weight / 2
}
}
Details worth knowing:
- Whole-number division.
weightis anInt, soweight / 2drops the remainder. Withweight = 7it gives 3, and the Economy cost is 6. - The empty parentheses. The methods are declared as
name()andcost(weight: Int), and an overriding method must match the declaration of the method it replaces. return. The solution writesreturnexplicitly. In Scala the last expression of a method is its result anyway, so"Standard"alone would also do.
Trying it
Create each option in a variable of the parent type and call the same two methods on all of them:
val options: List[Shipping] = List(new Shipping(), new Express(), new Economy())
for (option <- options) {
println(option.name() + ": " + option.cost(4))
}
This prints:
Standard: 9
Express: 18
Economy: 5
The loop never asks which class each option is. Other weights give these results:
- Economy with weight 7 gives
Economy: 6, and with weight 1 givesEconomy: 3. - Standard with weight 0 gives
Standard: 5, and Express with weight 0 givesExpress: 10. - Economy with weight 1000 gives
Economy: 503.
Why override instead of branching
Without inheritance, the code that prints a quote would need an if on the option: if it is express, use one formula; if it is economy, use another. Every new option would mean editing that code. With overriding, the decision is made once, by the object. The caller writes option.cost(weight) and each class supplies its own rule. Adding a fourth option means writing one more class that extends Shipping, and nothing that already works has to change.
It also explains why the parent has real bodies and not placeholders. Shipping is a complete, usable option: the standard one. The other two start from it and replace what differs. A subclass that overrides nothing behaves exactly like its parent.
Reading a call
Take val option: Shipping = new Express() and the call option.cost(3). The variable's type is Shipping, so the compiler accepts any method that Shipping has. At run time the object is an Express, so the express version runs and the result is 10 + 2 * 3 = 16.
Edge cases
A weight of 0 is allowed, and it exposes only the base price of each option: 5, 10 and 3. The largest weight is 1000 kilograms, where Economy costs 3 + 500 = 503. Every value stays tiny compared with the limit of an Int, so overflow is not a concern. The one case that needs care is an odd weight in Economy: 7 / 2 is 3, not 3.5, because both numbers are Int, so the half kilogram is dropped.
Another small trap is the order of operations in Express. In 10 + 2 * weight the multiplication happens first, which is what the rule asks for. Writing (10 + 2) * weight would be a different formula.
Common mistakes
- Forgetting
override. Writingdef name(): StringinExpresswithout the keyword is a compile error:method name of type (): String needs `override` modifier. - Changing the signature. A method with a different parameter type is a new method, not an override. Keep
cost(weight: Int): Intexactly as it is in the parent. - Dividing at the wrong place. Economy's formula is
3 + weight / 2. Rounding happens only in the division, so 7 kilograms cost 3 + 3 = 6. - Mixing up the base prices. Standard starts at 5, Express at 10 and Economy at 3. A weight of 0 shows them directly.
