Skip to content
wannacode

Interfaces without interface

Problem

Salaries and invoices

At the end of the month the company pays two kinds of things: the employees' salaries and the suppliers' invoices. You want one array and one loop: who gets paid, how much, and the total at the end.

An invoice is not an employee. Making it inherit from Employee is wrong. The sentence "an invoice is an employee" is not true, and the invoice would also get a name, a salary and a raise.

A common parent Payment with fields and code is not the right answer either: employees and invoices have no shared data and no shared code. All they have in common is that they can be paid.

The idea

An interface: a "can do" contract

An interface is a list of methods: what a class must be able to do, but not how. JavaScript has no interface, and it has no abstract either. The contract is just an agreement about the method names, written in a comment:

// The "can be paid" contract. Every payable object has:
// amountDue(): how much is owed
// payee(): to whom
A class that keeps the contract just writes the methods with those names. It does not tell anyone that it does:
class Invoice {
#supplier;
#amount;
constructor(supplier, amount) {
this.#supplier = supplier;
this.#amount = amount;
}
amountDue() {
return this.#amount;
}
payee() {
return this.#supplier;
}
}
const power = new Invoice("Power company", 340);
console.log(`${power.payee()}: €${power.amountDue()}`);
Output
Power company: €340

The language does not know about the contract. There is no implements: it is a reserved word, and a program with class Invoice implements Payable does not start (SyntaxError: Unexpected strict mode reserved word). So the names keep the contract: an object that has amountDue() and payee() can be paid. This is called duck typing: "if it walks like a duck and quacks like a duck, it is a duck".

See it

One loop for everything that gets paid

The employee accepts the contract too. Its amount is the monthly salary:

class Employee {
// ... #name, #salary, the constructor, monthlyPay()
amountDue() {
return this.monthlyPay();
}
payee() {
return this.#name;
}
}

Now both the employees and the invoices have amountDue() and payee(). The array has no type, and nothing connects them except the matching method names. That is enough for the loop.

const payments = [
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Invoice("Power company", 340),
new Invoice("Landlord", 1800),
];
let total = 0;
for (const p of payments) {
console.log(`${p.payee()}: €${p.amountDue()}`);
total += p.amountDue();
}
console.log(`Total: €${total}`);
Output
Ivan: €2000
Maria: €3500
Power company: €340
Landlord: €1800
Total: €7640

The manager writes nothing new. From Employee it inherits both the contract and amountDue(). And amountDue() calls monthlyPay(), which the manager has overridden. That is why Maria gets €3500.

What if an object that does not keep the contract ends up in the array? The ticket here has payee() but no amountDue():

class Ticket {
payee() {
return "Cinema";
}
}
const payments = [new Invoice("Power company", 340), new Ticket()];
for (const p of payments) {
console.log(`${p.payee()}: €${p.amountDue()}`);
}

The array takes it without question. The invoice gets paid, and on the ticket the program stops: TypeError: p.amountDue is not a function. You see the error only when the loop reaches the ticket, not when the ticket goes into the array.

const payments = [
new Employee("Ivan", 2000),
new Manager("Maria", 3000, 500),
new Invoice("Power company", 340),
];
let total = 0;
for (const p of payments) {
total += p.amountDue();
}
console.log(`Total: €${total}`);
Step by step, in words
  1. The array holds three objects of three classes. All they share is the method amountDue(), and the arrows reach only that.
  2. total starts at 0.
  3. p points to the employee: amountDue() returns the salary, 2000.
  4. The manager is an employee, so the manager can be paid too. Salary plus bonus: 3500.
  5. The invoice is not an employee, but it keeps the same contract: 340.
  6. The loop does not know the classes. It only knows that each one can be paid.

Try it

What is the amount?

What will the program print? The classes are from the card above.

const p = new Manager("Maria", 3000, 500);
console.log(p.amountDue());
Answer

The idea

One parent, many contracts

Accounting also wants the invoices in CSV format: one "supplier;amount" line each. That is a second contract. In JavaScript there is nothing to list: the invoice just writes that contract's method too.

// The second contract: toCsv() returns a "supplier;amount" line.
class Invoice {
// ... as before
toCsv() {
return `${this.#supplier};${this.#amount}`;
}
}
const forAccounting = [new Invoice("Power company", 340), new Invoice("Landlord", 1800)];
for (const e of forAccounting) {
console.log(e.toCsv());
}
Output
Power company;340
Landlord;1800

extends takes only one parent, but contracts are not parents. The manager stays extends Employee and just adds toCsv(). That way a class whose parent is already taken can still go into a common array with completely different classes.

Try it

Find the bug

The invoice is created and payee() works, but power.amountDue() stops the program. Which line has the bug?

Pick the line with the bug.

Try it

What is missing?

class Invoice {
  // ...

  () {
    return this.#amount;
  }
}

The idea

Abstract class or interface?

In JavaScript neither one is part of the language. An abstract parent is a class that stops with throw. A contract is just the method names. The difference is in what they say:

Abstract class Contract
Says "X is a Y" "X can"
How you write it extends a parent that throws an error nothing: just methods with those names
How many per class one as many as you want
Fields and constructor yes no
Shared code yes no
  • An abstract class, when the classes below it are one kind of thing and share code and data: shapes have a name and a description, employees have a name and a salary.
  • A contract, when the classes are different but need a common ability: an employee and an invoice "can be paid", a bird and a plane "can fly".
They are often used together: Employee is the parent of the manager and the intern, and at the same time it keeps the payment contract.
More precisely, if you are curious
  • In Java and C# an interface can have a default method, with a body. In JavaScript the contract does not exist as anything in the code, so there is nowhere to put such a method. You put the shared behavior in an object and copy it into the classes with Object.assign. Careful: the copy replaces a method with the same name if the class already has its own.
const gliding = {
glide() {
console.log("Gliding through the air");
},
};
class Bird {
fly() {
console.log("The bird flaps its wings");
}
}
class Plane {
fly() {
console.log("The plane takes off");
}
}
Object.assign(Bird.prototype, gliding);
Object.assign(Plane.prototype, gliding);
const flyers = [new Bird(), new Plane()];
for (const f of flyers) {
f.fly();
f.glide();
}
Output
The bird flaps its wings
Gliding through the air
The plane takes off
Gliding through the air
  • If you want to check the contract before you call the method, you ask whether the object has such a function: typeof p.amountDue === "function". There is more in the lesson "What is it really?".

Exercise

Contracts

Solve the tasks in your editor. In JavaScript the "interface" in each task is a contract in a comment: the classes just have the methods with those names.

1. Horn. An interface Honkable with a method honk(). SimpleCar from the module "Classes and objects" implements it.

Output
Beep beep!

2. Details. An interface Printable with a method printDetails(). Implement it in Person, and have Student (a child of Person) override it and add the grade. Put both of them in one array.

Output
Name: Georgi, age: 45
Name: Elena, age: 19
Average grade: 5.5

3. Dance. An interface Danceable with a method dance(). DanceMove (a name and a duration) implements it.

Output
Dancing salsa for 30 seconds

4. DJ. A class Song with a title. An interface Playable with a method play(). DJ implements it and has a method playSong(song) that remembers the song and plays it. If there is no song yet, play() says so.

Output
No song selected
Playing 'Bohemian Rhapsody'

5. The pond. Interfaces Swimmable with swim() and Flyable with fly(). Fish swims. Duck both swims and flies.

Output
The fish swims.
The duck swims.
The duck flies.

More tasks

  • Discount. An interface Discountable with a method that returns the price after the discount. Implement it in Product (10% off) and in Subscription (25% off for students). Work out the total of a cart with a €50 product and a €12 student subscription: €45 + €9 = €54.
  • CSV for everyone. Make Employee implement the CSV contract too, with a "name;monthly salary" line. Put employees and invoices in one array.

Test

Check yourself

1. In JavaScript, how does a class say that it keeps the "can be paid" contract?
2. An object without amountDue() ends up in the payments array. When will you find out?
3. The class writes amountdue() instead of amountDue(). What happens?
4. How many contracts can one class keep?
5. Employees and invoices have to be paid in one loop. They share no code. What fits better?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·