A curated list of common OOP design patterns.
This README.md is generated by running yarn start
.
Behavioral patterns are algorithms and the assignment of responsibilities between objects.
Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code.
Structural patterns explain how to assemble objects and classes into larger structures, while keeping this structures flexible and efficient.
-
Behavioral
-
Creational
-
Structural
abstract class Middleware {
protected next: Middleware | null = null;
public setNext(next: Middleware): void {
this.next = next;
}
public abstract run(request: Record<string, boolean>): void;
}
class Authentication extends Middleware {
public run(request: Record<string, boolean>): void {
if (request.isAuthenticated) {
this.next && this.next.run(request);
return;
}
console.log(401);
}
}
class Authorization extends Middleware {
public run(request: Record<string, boolean>): void {
if (request.isAdmin) {
this.next && this.next.run(request);
return;
}
console.log(403);
}
}
const authentication = new Authentication();
const authorization = new Authorization();
authentication.setNext(authorization);
// middlewares stack
const middlewares = authentication;
middlewares.run({ isAuthenticated: true, isAdmin: false }); // 403
interface Command {
execute(): void;
}
class ConcreteCommand implements Command {
#receiver: Receiver;
constructor(receiver: Receiver) {
this.#receiver = receiver;
}
public execute(): void {
this.#receiver.onReceive();
}
}
class Sender {
public execute(command: Command): void {
command.execute();
}
}
class Receiver {
public onReceive(): void {}
}
interface IIterator<T> {
next(): T;
hasNext(): boolean;
}
interface IIterable<T> {
iterator(): IIterator<T>;
}
class ConcreteIterator<T> implements IIterator<T> {
next(): T {}
hasNext(): boolean {}
}
class ConcreteIterable<T> implements IIterable<T> {
public iterator(): IIterator<T> {
return new ConcreteIterator();
}
}
interface Mediator {
notify(component: Component): void;
}
abstract class Component {
protected mediator: Mediator;
constructor(mediator: Mediator) {
this.mediator = mediator;
}
public abstract operate(): void;
}
class ConcreteMediator implements Mediator {
public notify(component: Component): void {}
}
class ConcreteComponent extends Component {
public operate(): void {
this.mediator.notify(this);
}
}
class Product {
#required: any;
public optionalA: any;
public optionalB: any;
public constructor(required: any) {
this.#required = required;
}
}
class Builder {
#product: Product;
public constructor(required: any) {
this.#product = new Product(required);
}
public setOptionalA(optionalA: any): Builder {
this.#product.optionalA = optionalA;
return this;
}
public setOptionalB(optionalB: any): Builder {
this.#product.optionalB = optionalB;
return this;
}
public build(): Product {
return this.#product;
}
}
const product = new Builder("required")
.setOptionalA("optionalA")
.setOptionalB("optionalB")
.build();
interface Component1 {}
class Component1A implements Component1 {}
class Component1B implements Component1 {}
interface Component2 {}
class Component2A implements Component2 {}
class Component2B implements Component2 {}
abstract class FactoryBase {
public assemble(): void {
const comp1 = this.createComponent1();
const comp2 = this.createComponent2();
}
protected abstract createComponent1(): Component1;
protected abstract createComponent2(): Component2;
}
class FactoryA extends FactoryBase {
protected createComponent1(): Component1 {
return new Component1A();
}
protected createComponent2(): Component2 {
return new Component2A();
}
}
class FactoryB extends FactoryBase {
protected createComponent1(): Component1 {
return new Component1B();
}
protected createComponent2(): Component2 {
return new Component2B();
}
}
interface Product {}
class ProductA implements Product {}
class ProductB implements Product {}
abstract class FactoryBase {
public abstract createProduct(): Product;
}
class FactoryA extends FactoryBase {
public createProduct(): Product {
return new ProductA();
}
}
class FactoryB extends FactoryBase {
public createProduct(): Product {
return new ProductB();
}
}
interface Product {}
class ProductA implements Product {}
class ProductB implements Product {}
class Factory {
public static createProduct(type: string): Product {
switch (type) {
case "A":
return new ProductA();
case "B":
return new ProductB();
default:
}
}
}
interface Prototype<T> {
clone(): T;
}
class Product implements Prototype<Product> {
public field: any;
public constructor();
public constructor(product: Product);
public constructor(product?: Product) {
if (product) {
this.field = product.field;
}
}
clone(): Product {
return new Product(this);
}
}
class Singleton {
private static instance: Singleton;
private constructor() {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
new Singleton(); //impossible
Singleton.getInstance() == Singleton.getInstance(); //true
interface X {}
interface Y {}
interface Z {}
class Client {
public userService(data: X): Z {
return new Adapter(new Service()).run(data);
}
}
class Adapter {
#service: Service;
public constructor(service: Service) {
this.#service = service;
}
public run(data: X): Z {
return this.#service.run(data);
}
}
class Service {
public run(data: Y): Z {}
}
abstract class Shape {
#color: Color;
public constructor(color: Color) {
this.#color = color;
}
public getColor(): void {
this.#color.getColor();
}
public abstract getShape(): void;
}
abstract class Color {
public abstract getColor(): void;
}
class Circle extends Shape {
public getShape(): void {
console.log("circle");
}
}
class Blue extends Color {
public getColor(): void {
console.log("blue");
}
}
const blueCircle = new Circle(new Blue());
blueCircle.getShape(); // circle
blueCircle.getColor(); // blue
interface Component {
execute(): void;
}
class Container implements Component {
#comps: Component[] = [];
public add(comp: Component) {
this.#comps.push(comp);
}
public execute(): void {
for (const comp of this.#comps) {
comp.execute();
}
}
}
class Leaf implements Component {
public execute(): void {}
}
interface Component {
execute(): void;
}
class ConcreteComponent implements Component {
public execute(): void {
console.log("from ConcreteComponent");
}
}
abstract class BaseDecorator implements Component {
#comp: Component;
public constructor(comp: Component) {
this.#comp = comp;
}
execute(): void {
this.#comp.execute();
}
}
class ConcreteDecoratorA extends BaseDecorator {
public execute(): void {
super.execute();
this.extra();
}
private extra(): void {
console.log("from ConcreteDecoratorA");
}
}
class ConcreteDecoratorB extends BaseDecorator {
public execute(): void {
super.execute();
this.extra();
}
private extra(): void {
console.log("from ConcreteDecoratorB");
}
}
const comp = new ConcreteComponent();
comp.execute();
const decoratedCompA = new ConcreteDecoratorA(comp);
decoratedCompA.execute();
const decoratedCompB = new ConcreteDecoratorB(decoratedCompA);
decoratedCompB.execute();
class ComponentA {
public static process() {}
}
class ComponentB {
public static process() {}
}
class Facade {
public static process() {
ComponentA.process();
ComponentB.process();
}
}
Facade.process();
class Forest {
#trees: Tree[];
#factory = new TreeTypeFactory();
plantTree({
x,
y,
name,
color,
texture,
}: {
x: number;
y: number;
name: string;
color: string;
texture: string;
}): void {
this.#trees.push(
new Tree(this.#factory, { x, y, name, color, texture })
);
}
}
class Tree {
public x: number;
public y: number;
public type: TreeType;
public constructor(
factory: TreeTypeFactory,
{
x,
y,
name,
color,
texture,
}: {
x: number;
y: number;
name: string;
color: string;
texture: string;
}
) {
this.x = x;
this.y = y;
// instead of directly constructing a new treeType
// fetch the treeType from factory
// to save memory usage
this.type = factory.getTreeType({ name, color, texture });
}
}
class TreeTypeFactory {
#cache: Record<string, TreeType>;
public getTreeType({
name,
color,
texture,
}: {
name: string;
color: string;
texture: string;
}): TreeType {
const key = this.hashKey({ name, color, texture });
if (!this.#cache[key]) {
this.#cache[key] = new TreeType({ name, color, texture });
}
return this.#cache[key];
}
private hashKey({
name,
color,
texture,
}: {
name: string;
color: string;
texture: string;
}): string {
return `${name}-${color}-${texture}`;
}
}
class TreeType {
#name: string;
#color: string;
#texture: string;
public constructor({
name,
color,
texture,
}: {
name: string;
color: string;
texture: string;
}) {
this.#name = name;
this.#color = color;
this.#texture = texture;
}
}
interface IDownloader {
download(query: string): string;
}
class Downloader implements IDownloader {
public download(query: string): string {
return query;
}
}
class DownloaderProxy implements IDownloader {
#downloader: Downloader;
#cache: Record<string, string>;
public constructor(downloader: Downloader) {
this.#downloader = downloader;
}
public download(query: string): string {
if (!this.#cache[query]) {
this.#cache[query] = this.#downloader.download(query);
}
return this.#cache[query];
}
}
const downloaderWithCache = new DownloaderProxy(new Downloader());