Problem overview
A shop has three ways to ship a parcel, and each answers the same two questions: what is the option called, and what does it cost for a given weight? Shipping is the standard option. Express and Economy inherit from it and give their own answers to both questions.
Shipping:name()is"Standard",cost(weight)is5 + weight.Express:"Express"and10 + 2 * weight.Economy:"Economy"and3 + weight / 2, with the division rounding down.
For a parcel of 4 kilograms that is 9, 18 and 5.
Approach
The parent class holds the standard rules. A subclass is written with : Shipping after its name, and each method it wants to replace is written again with the same header and the keyword override. The code that uses the options can then hold any of them in a variable of type Shipping and call name() and cost(_:). Swift runs the version that belongs to the object's real class, not to the variable's type.
Step by step
- In
Shipping,name()returns"Standard". - In
Shipping,cost(_ weight: Int)returns5 + weight. - In
Express,name()has the parent's header plusoverride, and returns"Express". - In
Express,costreturns10 + 2 * weight. - In
Economy,name()returns"Economy". - In
Economy,costreturns3 + weight / 2.
The solution in Swift
class Shipping {
func name() -> String {
return "Standard"
}
func cost(_ weight: Int) -> Int {
return 5 + weight
}
}
class Express: Shipping {
override func name() -> String {
return "Express"
}
override func cost(_ weight: Int) -> Int {
return 10 + 2 * weight
}
}
class Economy: Shipping {
override func name() -> String {
return "Economy"
}
override func cost(_ weight: Int) -> Int {
return 3 + weight / 2
}
}
Here the three options sit in one array of type [Shipping], and the same two calls run on each:
let options: [Shipping] = [Shipping(), Express(), Economy()]
for o in options { print("\(o.name()): \(o.cost(4))") }
print("--")
for o in options { print("\(o.name()): \(o.cost(0))") }
Standard: 9
Express: 18
Economy: 5
--
Standard: 5
Express: 10
Economy: 3
With a weight of 0 only the base prices are left: 5, 10 and 3.
Swift details worth knowing
overrideis required. A method that replaces the parent's must say so. Leaving it out is a compile error:overriding declaration requires an 'override' keyword.- Each class overrides only for itself. Changing
Expressdoes nothing toEconomyor toShipping. - Integer division rounds down. Both operands of
weight / 2areInt, so the remainder is dropped.
Edge cases and common mistakes
- Odd weights in Economy. Dividing 7 by 2 gives 3, and 1 by 2 gives 0, so the costs are 6 and 3.
- Forgetting one override. If
Expresshad nocost, an express parcel would get the parent's5 + weight: 12 for 7 kilograms instead of 24. - Weight 0. It is allowed, and the price is just the base.
- Different headers. An override must have the same name, parameter label and types as the parent's method. Here that is
cost(_ weight: Int) -> Intin all three classes; a different header would be a new, unrelated method.
Testing
Try these in a file of your own. On the solution above, Economy().cost(7) is 6, Economy().cost(1) is 3, Shipping().cost(7) is 12 and Express().cost(7) is 24, and a variable declared as let e: Shipping = Economy() still answers "Economy" to e.name().
Testing
Try these in a file of your own. On the solution above, a parcel of 4 kilograms prints Standard: 9, Express: 18 and Economy: 5, and a parcel of 0 kilograms prints Standard: 5, Express: 10 and Economy: 3. Beyond the numbers, check two things about the classes themselves:
- A
Shippingobject gives12forcost(7), while anExpressobject gives24: if both give 12, the override is missing. - An array typed
[Shipping]holding the three options must still print three different names. If all three printStandard, the subclasses' methods are not overriding the parent's.
Tracing the third example
The parcels are economy 7, economy 1, standard 7 and express 7. Each one builds the matching class and calls the same two methods:
- Economy with 7: the base is 3 and 7 / 2 is 3, so the cost is 6.
- Economy with 1: 1 / 2 is 0, so the cost is just the base, 3.
- Standard with 7: 5 + 7 is 12.
- Express with 7: 10 + 2 * 7 is 24.
The printed lines are Economy: 6, Economy: 3, Standard: 12 and Express: 24, as the parcels went in. Only the class decides the number, so a line for any weight from 0 to 1000 follows the same pattern.
Why use inheritance here
The alternative is one class with a text field for the kind and an if chain inside cost. Every new option then means editing that chain. With subclasses, a new option is a new class, and the code that calls name() and cost(_:) doesn't change. For example, an overnight option can reuse the standard price through super:
class Overnight: Shipping {{
override func name() -> String {{
return "Overnight"
}}
override func cost(_ weight: Int) -> Int {{
return super.cost(weight) + 20
}}
}}
let all: [Shipping] = [Shipping(), Express(), Economy(), Overnight()]
for o in all {{ print(o.name(), o.cost(7)) }}
Standard 12
Express 24
Economy 6
Overnight 32
super.cost(weight) runs the parent's version, 5 + 7 = 12, and the subclass adds 20 to it. The lesson's Express and Economy don't call super: each of them replaces the parent's formula completely, so they don't need to.
Reading the same parcel across the options
For a parcel of 7 kilograms, the four calls above show how one weight gives four prices. Standard is the base plus one per kilogram, Express charges double per kilogram, and Economy charges only for half the weight. Because Economy rounds down, 6 and 7 kilograms cost the same there, while the other options go up with every kilogram.
Key takeaways
- A subclass replaces a parent's method by repeating its header with
override. - Calling a method through the parent's type runs the object's own version.
