Start learning free

LESSON 563 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Shipping Options in TypeScript: Inheritance and Overriding

Practice O(1) Read the full explanation

An online shop offers three ways to ship a parcel. Standard shipping is the basic one. Express and Economy are kinds of shipping too: they answer the same two questions, what the option is called and what it costs, but each in its own way. So each one overrides the parent's methods with its own versions.

Implement the class Shipping, the standard option:

  • name() returns "Standard".
  • cost(weight) returns 5 + weight: a base price of 5, plus 1 for each kilogram.

and two classes that inherit from Shipping and override both methods:

  • Express: name() returns "Express", and cost(weight) returns 10 + 2 * weight.
  • Economy: name() returns "Economy", and cost(weight) returns 3 plus half the weight, rounded down to a whole number: half of 7 is 3.

The input lists parcels, one per line: the option (standard, express or economy) and the weight. For each line, the code that tests your classes creates that option and prints <name>: <cost> from its name() and cost(weight). The two calls are the same for every option (where variables have types, through a variable of the parent type Shipping); because the methods are overridden, each option answers with its own version. The starter code spells these names the way your language does.

 

Example 1:

Input:
    standard 4
    express 4
    economy 4
Output:
    Standard: 9
    Express: 18
    Economy: 5
Explanation: The same parcel of 4 kilograms costs 5 + 4 = 9 with Standard, 10 + 2 × 4 = 18 with Express
and 3 + 2 = 5 with Economy (half of 4 is 2).

Example 2:

Input:
    standard 0
    express 0
    economy 0
Output:
    Standard: 5
    Express: 10
    Economy: 3
Explanation: With a weight of 0, only the base prices are left.

Example 3:

Input:
    economy 7
    economy 1
    standard 7
    express 7
Output:
    Economy: 6
    Economy: 3
    Standard: 12
    Express: 24
Explanation: Economy halves the weight and rounds down: half of 7 is 3, so 3 + 3 = 6, and half of 1 is 0, so 3 + 0 = 3.

 

Constraints:

  • 0 <= weight <= 1000, a whole number of kilograms.
  • At most 100 parcels per test case.
STEP 1 OF 6
    Ctrl + Enter to check

    TYPESCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Shipping Options in TypeScript, explained

    Problem overview

    A shop ships parcels in three ways. Shipping is the standard option: name() returns "Standard" and cost(weight) returns 5 + weight. Express and Economy extend Shipping and replace both methods with their own versions. Express charges 10 + 2 * weight. Economy charges 3 plus half the weight, rounded down.

    For a 4 kilogram parcel, the three options answer like this:

    Standard: 9
    Express: 18
    Economy: 5

    Approach: one parent, two overrides

    All three options answer the same two questions, so they share one shape: the parent class defines name and cost, and each child class declares methods with the same names and parameters. A method in a child with the same name as one in the parent overrides it. When you call name() on an Express object, the Express version runs, even if the variable holding the object is typed as Shipping.

    That is what makes the calling code simple. It can hold a list of Shipping values, call name() and cost(weight) on each one, and get the right answer for every kind, with no if on the option's kind.

    Step by step

    1. In Shipping, make name return "Standard".
    2. Make cost return 5 + weight: the base price plus 1 per kilogram.
    3. In Express, write a name with the same header as the parent's. It returns "Express".
    4. Write Express's own cost: 10 + 2 * weight. Without it, an express parcel would get the parent's price.
    5. In Economy, override name to return "Economy". Each class overrides only for itself, so nothing here changes Express or Shipping.
    6. Override cost with 3 + Math.floor(weight / 2).

    The solution in TypeScript

    class Shipping {
        name(): string {
            return "Standard";
        }
    
        cost(weight: number): number {
            return 5 + weight;
        }
    }
    
    class Express extends Shipping {
        name(): string {
            return "Express";
        }
    
        cost(weight: number): number {
            return 10 + 2 * weight;
        }
    }
    
    class Economy extends Shipping {
        name(): string {
            return "Economy";
        }
    
        cost(weight: number): number {
            return 3 + Math.floor(weight / 2);
        }
    }
    

    TypeScript details worth knowing:

    • / keeps the fraction. In JavaScript and TypeScript every number is a double, so 7 / 2 is 3.5. Math.floor rounds it down to 3. For these weights (whole, non-negative numbers) that is the "round down" the lesson asks for.
    • No constructors needed. The classes have no fields, so the default constructor is enough, and new Express() works as it is.
    • Overriding is by name. A child's method must be compatible with the parent's: same name, and a parameter and return type that fit. TypeScript doesn't need an override keyword here.
    • Types vanish at run time. The annotation Shipping on a variable only tells the compiler what it may call. The JavaScript that runs looks the method up on the actual object, which is why the child's version wins.

    Seeing the override work

    Put the three objects in an array typed Shipping[] and ask each one the same questions:

    const options: Shipping[] = [new Shipping(), new Express(), new Economy()];
    for (const o of options) console.log(o.name() + ": " + o.cost(4));
    for (const o of options) console.log(o.name() + ": " + o.cost(0));
    console.log(new Economy().cost(7), new Economy().cost(1));

    This prints:

    Standard: 9
    Express: 18
    Economy: 5
    Standard: 5
    Express: 10
    Economy: 3
    6 3

    With weight 0 only the base prices are left. For Economy, weight 7 gives 3 + 3 = 6 and weight 1 gives 3 + 0 = 3.

    Why the child's version runs

    A TypeScript class compiles to a JavaScript class, and extends links the child's prototype to the parent's. When you call x.name(), JavaScript looks for name on the object's own class first and walks up to the parent only if it finds nothing. So a method written in Express is found before the one in Shipping, and a method that Express doesn't write is inherited from Shipping.

    Declare const x: Shipping = new Express(); and the compiler lets you call only what Shipping has, but the object is still an Express: x.name() returns "Express" and x.cost(1) returns 12, and x instanceof Express is true. This is why the code that tests your classes can treat every option the same way and still get three different answers.

    Inheritance also means a child never has to repeat what it shares with the parent. Here the children share only the method names, because both methods are overridden. If a child wrote only name, its cost would come from Shipping unchanged.

    Edge cases and common mistakes

    • Forgetting to override cost. A child class that overrides only name inherits the parent's price. A class Bad extends Shipping with a working cost but no name answers name() with "Standard": it prints Standard 18 for a weight of 4 when the cost is 10 + 2 * w. Each method needs its own override.
    • Forgetting Math.floor. Without it, Economy's cost for weight 7 is 6.5, and for weight 1 it is 3.5.
    • Wrong spelling in the text. The names are returned as they are, so "Express" and "express" are different strings.
    • Changing the parent to fit a child. Leave Shipping as the standard option. Express and Economy carry their own rules.
    • The largest weight. At 1000 kilograms, Economy charges 503 and Express charges 2010. Plain numbers hold these easily, so there is nothing to guard against, as long as each formula is written exactly.
    • Copying one child to make another. If you paste Express's class to start Economy, change both the name text and the formula. A leftover 10 + 2 * weight makes the two classes agree on the price and the lesson's tests fail on the economy lines.
    • Weight 0. Valid input: it leaves just the base prices, 5, 10 and 3.

    Testing

    • weight 4 gives Standard: 9, Express: 18, Economy: 5.
    • weight 0 gives Standard: 5, Express: 10, Economy: 3.
    • weight 7 gives Economy: 6, Standard: 12, Express: 24.
    Keep going with TypeScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free