The interface is a signature. It is a contract that implementations agree to adhere to. Using an interface, you have guarantees that certain properties or functions exist regardless of how the object is actually implemented.
The class is an implementation. Unlike an interface, a class can be instantiated. It contains the behaviors of that class, as well as the implementations of any interface it extends. Classes can also participate in inheritance. For example, in my 6502 chipset emulator written in TypeScript (github.com/JeremyLikness/redux6502) the interface IOpCode is a contract that op-codes can implement:
export interface IOpCode {
name: string;
value: OpCodeValue;
mode: AddressingModes;
size: Byte;
execute: (cpu: ICpu) => void;
}
The base class is default behavior shared by all op codes (a partial implementation):
export class BaseOpCode implements IOpCode {
private _execute: (cpu: ICpu) => void = cpu => {
throw new Error('Not implemented!');
}
constructor(
public name: string,
public value: OpCodeValue,
public mode: AddressingModes,
public size: Byte,
logic: (cpu: ICpu) => void) {
this._execute = logic;
}
public execute(cpu: ICpu): void {
this._execute(cpu);
}
}
And then each op code can inherit and override behavior explicit to that operation:
export class InvalidOpCode extends BaseOpCode {
constructor(public value: OpCodeValue) {
super(INVALID, value, AddressingModes.Single, 0x01, cpu => { });
}
}
The interface makes it possible for me to test higher order functions with my own stub or mock object to take the place of a "real" op code. The tests can interact using the interface without concern for how the implementation happens.
Interface: signature. "It should look like this and implement these things." Class: implementation. "It is an instance that does these things."
Hope that helps!