Problem overview
A company pays every employee a salary. A manager is an employee too, but earns a bonus on top. You write two classes so that a manager reuses everything an employee has and changes only how its pay is worked out.
The class Employee has:
- a constructor taking a name and a salary, both private to
Employee; pay(), which returns the salary;describe(), which returns the text<name> earns <pay>, using whatpay()returns.
The class Manager inherits from Employee. It takes a name, a salary and a bonus, passes the first two to the parent and keeps the bonus privately. Its pay() returns the parent's pay plus the bonus. describe() is not written again.
An employee Ana with salary 3000 is described as Ana earns 3000. A manager Bob with salary 4000 and bonus 1000 is described as Bob earns 5000.
Approach: override one method, inherit the rest
The only difference between the two kinds of people is how pay is computed. So only pay changes. Manager overrides it, and everything else (the stored name and salary, and describe) is inherited unchanged.
The key point is that describe calls pay() instead of reading the salary directly. When describe runs on a manager, the call pay() goes to the manager's version, so the bonus shows up without a second describe. This is called polymorphism: the same call runs different code depending on the actual kind of object.
The second point is that the salary is private to Employee, so Manager cannot read it. It does not need to: super.pay() asks the parent's version, which knows how an employee is paid, and the manager adds its bonus to that.
Step by step
- Employee's header.
open class Employee(private val name: String, private val salary: Int). Theopenkeyword is needed because Kotlin classes are final by default: without it no class could inherit fromEmployee. Theprivate valparameters become private properties. - pay in Employee. It is marked
opentoo, since a method also has to allow overriding. It returnssalary. - describe. Return a string template:
"$name earns ${pay()}". The$nameinserts a variable, and${...}inserts the value of any expression, here a method call. It callspay(), notsalary. - Manager's header.
class Manager(name: String, salary: Int, private val bonus: Int) : Employee(name, salary). After the colon,Employee(name, salary)calls the parent's constructor.nameandsalaryare plain parameters here (noval), because the parent stores them. Onlybonusis a property. - pay in Manager. Mark it
overrideand returnsuper.pay() + bonus.
The solution in Kotlin
open class Employee(private val name: String, private val salary: Int) {
open fun pay(): Int {
return salary
}
fun describe(): String {
return "$name earns ${pay()}"
}
}
class Manager(name: String, salary: Int, private val bonus: Int) : Employee(name, salary) {
override fun pay(): Int {
return super.pay() + bonus
}
}
Notice what is missing from Manager: there is no describe and no stored name or salary. Notice also what is marked: open on the class and on pay in the parent, override on pay in the child. describe is not open, so nobody can override it by accident.
Using the classes
fun main() {
val people = listOf(Employee("Ana", 3000), Manager("Bob", 4000, 1000), Manager("Cleo", 5000, 0))
for (x in people) println(x.describe())
val e: Employee = Manager("Eve", 7000, 2500)
println(e.pay())
}
It prints:
Ana earns 3000
Bob earns 5000
Cleo earns 5000
9500
The list holds both kinds, and the loop calls describe() on each. A manager with a bonus of 0, like Cleo, earns exactly the salary. The last lines show that a variable of type Employee can hold a manager, and e.pay() still runs the manager's version: 7000 plus 2500.
What the compiler says
Three mistakes come up often, and Kotlin catches each one at compile time.
- Reading the private salary in
Manager, as inreturn salary + bonus, fails: the compiler says it cannot accesssalarybecause it is private inEmployee. Usesuper.pay(). - Forgetting
openon the parent'spayfails with:'pay' in 'Employee' is final and cannot be overridden. - Calling
pay()insideManager.payinstead ofsuper.pay()compiles, but the method calls itself forever and the program ends with aStackOverflowError.
Common mistakes
- Reading
salaryindescribe. Then managers would be described with their salary only, the bonus missing. - Rewriting
describeinManager. It works, but it copies code the child gets for free, and the two copies can drift apart. - Adding
valtonameandsalaryinManager's header. It compiles, but it declares second, public properties that store the same data again, which breaks the lesson's rule that these values are private toEmployee. Plain parameters are enough. - Forgetting
override. Kotlin requires it, so the compiler tells you when a method meant to replace the parent's does not match its signature.
