Problem overview
A company pays each employee a salary. A manager is an employee too, with a bonus on top. The task is two classes in which Manager reuses everything Employee has and changes only how the pay is worked out.
Employee(name, salary)keeps both private.pay()returns the salary.describe()returns text likeAna earns 3000, using whatpay()returns.Manager(name, salary, bonus)extendsEmployee. It stores the bonus privately, and itspay()returns the parent's pay plus the bonus. It doesn't rewritedescribe().
Approach
Each class owns its own data. The salary is private to Employee, so Manager can't read it. It doesn't need to: super.pay() runs the parent's version, which knows how to give the salary. describe() calls this.pay(). On a manager, this.pay() is the manager's version, so the inherited describe() shows the bonus without any extra code.
Step by step
- Employee's private fields
nameandsalary. Private means not evenManagercan use them. - Constructor stores both parameters in the fields.
- pay returns
this.salary. - describe returns the name, then
" earns ", thenthis.pay(), joined with+. Adding a number to a string turns the number into text. - Manager
extends Employeeand declares its own privatebonus. - Constructor. First
super(name, salary), which runs the parent's constructor and fills in the private name and salary. TypeScript requires this call before the constructor usesthis. - Store the bonus in
this.bonus. - Override pay with
super.pay() + this.bonus.
The solution in TypeScript
class Employee {
private name: string;
private salary: number;
constructor(name: string, salary: number) {
this.name = name;
this.salary = salary;
}
pay(): number {
return this.salary;
}
describe(): string {
return this.name + " earns " + this.pay();
}
}
class Manager extends Employee {
private bonus: number;
constructor(name: string, salary: number, bonus: number) {
super(name, salary);
this.bonus = bonus;
}
pay(): number {
return super.pay() + this.bonus;
}
}
super.pay()versusthis.pay(). InsideManager.pay,this.pay()would call the same method again, forever.super.pay()callsEmployee's version.- Overriding. A method with the same name in the subclass replaces the parent's for objects of the subclass, even when the call is made inside the parent's code, as in
describe. - Private is for the compiler. A line such as
b.salaryoutside the class is reported as an error (the property is private and only accessible withinEmployee), but TypeScript still writes JavaScript and the value would be read at run time.
Using the classes
const a = new Employee("Ana", 3000);
const b = new Manager("Bob", 4000, 1000);
console.log(a.describe());
console.log(b.describe());
console.log(new Manager("Cleo", 5000, 0).describe());
console.log(new Manager("Hana", 6000, 500).describe());
const staff: Employee[] = [a, b];
for (const s of staff) {
console.log(s.pay());
}
Ana earns 3000
Bob earns 5000
Cleo earns 5000
Hana earns 6500
3000
5000
A manager can be used wherever an employee is expected: in the list typed Employee[], the call s.pay() picks the right version for each object. A manager with a bonus of 0 earns exactly the salary.
How the call finds the right pay
A single call such as describe() can run different code depending on the object. Take a list that mixes both kinds of people:
| Object | describe() returns |
|---|---|
new Employee("Ana", 3000) | Ana earns 3000 |
new Manager("Bob", 4000, 1000) | Bob earns 5000 |
new Manager("Eve", 7000, 2500) | Eve earns 9500 |
new Employee("Finn", 2800) | Finn earns 2800 |
There is only one describe, written in Employee. When it runs on Bob it calls this.pay(), and because Bob is a Manager object, that call goes to Manager's pay. A manager is also an employee: instanceof Employee is true for both, while instanceof Manager is true only for the managers.
This is the reason to build describe on pay() and not on the field: a change to how managers are paid then needs no change to describe.
What goes wrong without super
Two natural mistakes are worth seeing. If Manager.pay calls this.pay() for the base amount, the method calls itself, and the program stops with RangeError: Maximum call stack size exceeded. If Manager.pay returns only this.bonus, the program runs, but Bob's description reads Bob earns 1000: the salary is lost. Writing super.pay() + this.bonus avoids both, because it asks the parent for exactly the part the parent owns.
The same structure works for any "base plus extra" rule. The subclass adds its own data and its own change to one method, and everything else is inherited.
Edge cases and common mistakes
- Forgetting
super(...). A subclass constructor must call it before usingthis, or the compiler reports an error. - Reading
this.salaryindescribe. It would ignore the bonus for managers. Callthis.pay(). - Writing
describeagain inManager. It is unnecessary; the inherited one already works. - Using
this.pay()insideManager.pay. It recurses until the program runs out of stack.
More cases to try
- A salary of 0 and a bonus of 0 still describe as
earns 0. The text comes from joining a string with a number, and 0 is a valid number to print. - Two people may share a name. Each object keeps its own private fields, so
Anathe employee andAnathe manager do not affect each other. - Building a manager with the arguments in the wrong order, such as bonus before salary, compiles, because all three are in the right types, and gives the wrong pay. Check the order against the signature: name, salary, bonus.
Key points
extendsmakesManageranEmployeethat inherits its fields and methods.- A subclass constructor calls
super(...)first, so the parent can set up the fields it owns. - A private field belongs to the class that declares it. The subclass gets that data through the parent's methods, here
super.pay(). - Overriding changes one method for the subclass and leaves the rest as inherited.
- Inherited code that calls
this.pay()picks up the override, sodescribeneeds no second copy.
Design the parent so that its methods call each other through this, and the subclasses can change behavior by overriding a single method.
