Problem overview
A shop ships parcels in three ways. Shipping is the standard option, and Express and Economy are kinds of shipping too. Every option answers the same two questions, name() and cost(weight), but each answers in its own way:
| 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 5 + 4 = 9, Express costs 10 + 2 * 4 = 18, and Economy costs 3 + 4 / 2 = 5. With a weight of 0 only the base prices remain: 5, 10 and 3.
Approach: a parent class and two subclasses
The three options share a shape, two methods with the same names, so they share a parent. Express and Economy are written as class Express extends Shipping, which means an Express object is also a Shipping object. The subclass can then override a method: it declares a method with the same name, and JavaScript uses that version for objects of the subclass.
The lookup works like this. When you call name() on an Express object, JavaScript looks for the method in Express first. It finds one, so it runs it. Only if Express had no name would JavaScript continue to Shipping. That is why a subclass that forgets to override cost silently charges the parent's price.
The payoff comes at the call site. Code that holds a parcel's option can call option.name() and option.cost(weight) without knowing which class the option is, and each object answers with its own version.
Step by step
- Write
Shipping.name. It returns the text"Standard". - Write
Shipping.cost. It returns the base price 5 plus one per kilogram,5 + weight. - Override
nameinExpress. Same header, new text:"Express". - Override
costinExpress. It returns10 + 2 * weight. Without it, an express parcel would get the parent's5 + weight. - Override
nameinEconomy. It returns"Economy". Each class overrides only for itself, so nothing here changesExpressorShipping. - Override
costinEconomy. It returns 3 plus half the weight, rounded down withMath.floor.
The solution in JavaScript
class Shipping {
name() {
return "Standard";
}
cost(weight) {
return 5 + weight;
}
}
class Express extends Shipping {
name() {
return "Express";
}
cost(weight) {
return 10 + 2 * weight;
}
}
class Economy extends Shipping {
name() {
return "Economy";
}
cost(weight) {
return 3 + Math.floor(weight / 2);
}
}
None of the three classes has a constructor, because the options store nothing: the price depends only on the weight passed to cost. JavaScript supplies a default constructor, so new Express() works.
Why Math.floor is needed
JavaScript has one number type, and / never rounds. 7 / 2 is 3.5, not 3. The lesson wants the division rounded down, so Economy.cost wraps it: Math.floor(7 / 2) is 3. Weights are never negative here, so rounding down and truncating agree.
Using the classes
Calling the methods through one list shows overriding at work:
const options = [new Shipping(), new Express(), new Economy()];
for (const option of options) {
console.log(option.name() + ": " + option.cost(7));
}
This prints:
Standard: 12
Express: 24
Economy: 6
The loop body is identical for all three objects. Only the objects differ. Economy with a weight of 1 gives 3 + Math.floor(1 / 2), which is 3, and an Economy object is still a Shipping object: new Economy() instanceof Shipping is true.
Why inheritance helps here
Three unrelated classes would also work for the tests, because JavaScript does not check types before running. The difference is in what the code says. With extends Shipping, the program states that Express and Economy are kinds of shipping, and a reader knows that anything written for a Shipping object, such as a function that prints a quote, accepts them too. A fourth option, say overnight delivery, needs only another subclass that overrides the two methods.
The overrides here replace the parent's code completely. The parent's cost is never run for an express parcel. Inside a method, super.cost(weight) would call the parent's version on purpose, but this lesson does not need it, because Express and Economy compute their price from scratch.
Tracing Economy
Economy is the only class where rounding matters. For weights 0, 1, 4 and 7, Math.floor(weight / 2) gives 0, 0, 2 and 3, so the costs are 3, 3, 5 and 6. Weights 6 and 7 give the same cost, 6, because the odd kilogram does not count.
Edge cases and common mistakes
- Forgetting one override. If
Expressoverridesnamebut notcost, an express parcel of 4 kilograms costs 9, the standard price, and nothing reports an error. A subclass that only overridesnameand is asked forcost(4)gets9. - Dropping
Math.floor. Without it, Economy with weight 7 returns 6.5 instead of 6, and any odd weight gives a fraction. - Misreading the order of operations.
10 + 2 * weightmultiplies first, as in arithmetic. No parentheses are needed. - Weight 0. Every class returns just its base price: 5, 10 and 3.
- Returning instead of printing. The methods return the name and the cost. The lesson's tests do the printing.
Key takeaways
extendsmakes a subclass a kind of its parent.- A method with the same name in the subclass replaces the parent's for that class only.
- Code that calls
name()andcost(weight)works with any option, whichever class it is.
