Interfaces
— OOP, TypeScript, JavaScript
interface is a keyword that allows us to separate class interface from its implementation.
Interface defines the contract which should be fulfilled by the entity that implements this interface.
The above definition is the most common use of interfaces in languages like C# or Java.
Consider the implementation of the Stack class. The stack is an abstract data type that serves as a collection of elements.
The stack can be implemented in many ways, i.e. the values of the stack can be stored in arrays, lists or binary trees, the stack can have fixed size and so on.
Independently of the implementation, Stack class definition should contain methods like push and pop and this is our interface.
1export interface IStack<T> {2 push(item: T): void,3 pop(): T | undefined,4 firstElement(): T | undefined,5 lastElement(): T | undefined6}
1import { IStack } from './IStack'23export class Stack<T> implements IStack<T> {4 constructor(private stack: T[] = []) {}56 push(item: T) {7 this.stack.unshift(item)8 } 910 pop(): T | undefined {11 return this.stack.shift()12 }1314 firstElement(): T | undefined {15 return this.stack[0]16 }1718 lastElement(): T | undefined {19 return this.stack[this.stack.length - 1]20 }21}
1import { deepStrictEqual } from 'assert'2import { Stack } from './Stack'34const stringStack = new Stack<string>()5stringStack.push('foo')6stringStack.push('bar')7deepStrictEqual(stringStack.lastElement(), 'foo')8deepStrictEqual(stringStack.pop(), 'bar')910const numberStack = new Stack<number>()11numberStack.push(1)12numberStack.push(2)13deepStrictEqual(numberStack.lastElement(), 1)14deepStrictEqual(numberStack.pop(), 2)
Interfaces are capable of describing the wide range of shapes that JavaScript objects can take.
For example, we can create the User interface which will describe the shape of the user object and then we can refer to this type in multiple places.
1interface User {2 username: string,3 password: string,4 avatarURL?: string,5 birthdate?: Date,6}78const createUser = (user: User): void => { /* Any logic */ }9createUser({ username: 'foo', password: 'baz' })10createUser({ username: 'foo' }) // Shows error property 'password' is missing1112const user: User = {13 username: 'foo',14 password: 'bar',15 avatarURL: 'https://giphy.com/gifs/duck-kitten-ns9NbiMArJgkw'16}1718const invalidUser: User = { password: 'foo' } // Shows error
Be aware that the TypeScript represents the Structural Typing system also called Duck Typing.
Shortly, it means that the TypeScript cares only about the shape of objects. The object's type itself is not important.
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
1interface Car {2 id: number,3 run(): void4}56interface Job {7 id: number,8 run(): void9}1011const car: Car = {12 id: 100,13 run() { console.log(`Starting the engine...`) }14}1516const job: Job = {17 id: 1,18 run() { console.log(`Processing the job...`) }19}2021const start = (job: Job) => job.run()2223start(job)2425// TypeScript compiler will not throw an error, even if the start function expects the Job type as an argument, 26// because the car has the same shape as the job object. 27start(car)
With interfaces, we can also define the function signatures
1import { readFile } from 'fs'23interface Callback {4 (error: Error | null, data: string): void5}67const callback: Callback = (error, data) => {8 if (error) throw error9 data.search('foo')10}1112readFile('/src/file.txt', 'utf8', callback)1314// This code will show an error.15// We didn't define the encoding option. 16// Our callback will receive the Buffer instead of a string. 17// This code is not compatible with our interface definition.18readFile('/src/file.txt', callback)
Interfaces allow us also to define indexable types
1interface StatusCodes {2 [index: string]: number3}45const statusCodes: StatusCodes = {6 'OK': 200,7 'NOT_FOUND': 400,8 'INTERNAL_SERVER_ERROR': 5009}1011statusCodes['NOT_FOUND']
Interfaces, like classes, are extensible. An interface can also extend multiple interfaces.
1interface Person { name: string }2interface Employee extends Person { job: string }3interface Boss extends Person, Employee { title: string }45const person: Person = { name: 'foo' }6const employee: Employee = { name: 'foo', job: 'bar' }7const boss: Boss = { name: 'foo', job: 'bar', title: 'baz' }