Like in some other OO languages, in typescript it's also not possible to have multiple inheritance but it's possible to implement more than one interface, that's one use case for which interfaces are good for.
Another reason is if you want to spread different implementations across different namespaces/modules but you want them all to implement a specific set of methods:
namespace callbacks {
export interface Callback<T> {
getName(): string;
execute(): T;
}
}
namespace mynamespace1 {
export class Callback implements callbacks.Callback<string> {
public getName(): string {
return "mynamespace1.Callback";
}
public execute(): string {
return "executed";
}
}
}
namespace mynamespace2 {
export class Callback implements callbacks.Callback<boolean> {
public getName(): string {
return "mynamespace2.Callback";
}
public execute(): boolean {
return true;
}
}
}
But the best reason (in my opinion) is that it lets you hide the implementing classes inside a closure so that no one can create them directly but only via factory functions or some actions:
namespace logging {
const httpLoggingEndpoint: URL = new URL(...);
const fileLoggingFilePath: string = "LOG_FILE_PATH";
export enum LoggerType {
Console,
Http,
File
}
export interface Logger {
log(message: string): void;
}
export function getLogger(type: LoggerType): Logger {
switch (type) {
case LoggerType.Console:
return new ConsoleLogger();
case LoggerType.Http:
return new HttpLogger();
case LoggerType.File:
return new FileLogger();
}
}
class ConsoleLogger implements Logger {
public log(message: string): void {
console.log(message);
}
}
class HttpLogger implements Logger {
public log(message: string): void {
// make a request to httpLogingEndpoint
}
}
class FileLogger implements Logger {
public log(message: string): void {
// log message to the file in fileLoggingFilePath
}
}
}
This way no one can directly instantiate a logger because none of the actual classes are exported.
Another point to make on this subject is that in typescript classes can be treated as interfaces:
class Logger {
public log(message: string) {
console.log(message);
}
}
class HttpLogger implements Logger {
public log(message: string) {
// log using an http request
}
}
Which is used for mixins for example, so in practice my first two scenarios can be done with classes as well, though my last example won't work as well with classes because then you can just instantiate the base class and by doing that bypass the "safety mechanism" of not being able to directly calling the different constructors.