Design Patterns in TypeScript: Advanced Level with Examples
Once basic patterns (Singleton, Factory Method, Observer) are mastered, it's time for more complex architectural solutions. TypeScript, with its powerful type system, generics, and decorators, opens up new possibilities for implementing advanced GoF patterns. In this article, we'll break down four key patterns: Abstract Factory, Builder, Chain of Responsibility, and Strategy — with an emphasis on TypeScript-specific features.
1. Abstract Factory with Typed Families
The Abstract Factory pattern allows creating families of related objects without binding to specific classes. In TypeScript, we can use interfaces and generics for strict product typing.
Example: Creating UI components for different operating systems.
// Abstract productsinterface Button { render(): void; click(): void;}
interface Checkbox { render(): void; toggle(): void;}
// Concrete products for Windowsclass WinButton implements Button { render() { console.log('Rendering Windows button'); } click() { console.log('Clicking Windows button'); }}
class WinCheckbox implements Checkbox { render() { console.log('Rendering Windows checkbox'); } toggle() { console.log('Toggling Windows checkbox'); }}
// Concrete products for macOSclass MacButton implements Button { render() { console.log('Rendering macOS button'); } click() { console.log('Clicking macOS button'); }}
class MacCheckbox implements Checkbox { render() { console.log('Rendering macOS checkbox'); } toggle() { console.log('Toggling macOS checkbox'); }}
// Abstract factoryinterface GUIFactory { createButton(): Button; createCheckbox(): Checkbox;}
// Concrete factoriesclass WinFactory implements GUIFactory { createButton(): Button { return new WinButton(); } createCheckbox(): Checkbox { return new WinCheckbox(); }}
class MacFactory implements GUIFactory { createButton(): Button { return new MacButton(); } createCheckbox(): Checkbox { return new MacCheckbox(); }}
// Usagefunction createUI(factory: GUIFactory) { const button = factory.createButton(); const checkbox = factory.createCheckbox(); button.render(); checkbox.render();}
createUI(new WinFactory());// Rendering Windows button// Rendering Windows checkboxAdvanced technique: Use generics if the factory should work with different product types:
abstract class AbstractFactory<TProduct> { abstract createProduct(): TProduct;}
class ConcreteFactory extends AbstractFactory<Button> { createProduct(): Button { return new WinButton(); }}2. Builder with Fluent Interface and Immutability
The Builder pattern is useful when an object has many optional parameters. TypeScript allows creating a type-safe builder with a method chaining.
Example: HTTP request configuration.
class HttpRequest { private constructor( public readonly url: string, public readonly method: 'GET' | 'POST' | 'PUT', public readonly headers: Record<string, string>, public readonly body?: string ) {}
static builder(): HttpRequestBuilder { return new HttpRequestBuilder(); }}
class HttpRequestBuilder { private url: string = ''; private method: 'GET' | 'POST' | 'PUT' = 'GET'; private headers: Record<string, string> = {}; private body?: string;
setUrl(url: string): this { this.url = url; return this; }
setMethod(method: 'GET' | 'POST' | 'PUT'): this { this.method = method; return this; }
addH