Start learning free

LESSON 563 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Shipping Options in Kotlin: 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

    KOTLIN · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Shipping Options in Kotlin, explained

    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.

    Classname()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:

    • open on the parent class and on each method that children may replace;
    • override on 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

    1. Write Shipping, marked open, with name() returning "Standard". The method is open too, so children may replace it.
    2. Give it cost(weight), returning 5 + weight.
    3. In Express, add override fun name() returning "Express".
    4. Override cost in Express to return 10 + 2 * weight. Without it, an express parcel would be priced by the parent's formula.
    5. In Economy, override name() to return "Economy".
    6. Override cost in Economy to return 3 + weight / 2. Both operands are Int, so the division drops the remainder: 7 / 2 is 3.

    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 no override is a compile error too.
    • Forgetting () after the parent. The header is : Shipping(), not : Shipping.
    • Dividing the wrong way. 3 + weight / 2 halves the weight only. Writing (3 + weight) / 2 halves 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 cost that takes a Long would be a different method, not a replacement.
    • Overriding only one method. If Express overrides name but not cost, it reports "Express" with the standard price.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free