Skip to content

Instantly share code, notes, and snippets.

@im4aLL
Last active April 5, 2024 00:21
Show Gist options
  • Save im4aLL/0a1488e28b076eaef4c54744275cecf8 to your computer and use it in GitHub Desktop.
Save im4aLL/0a1488e28b076eaef4c54744275cecf8 to your computer and use it in GitHub Desktop.
Design pattern in typescript
// The Builder pattern is a creational design pattern that allows the construction of complex objects step by step. It separates the construction of a complex object from its representation, enabling the same construction process to create different representations.
class Pizza {
private size: string;
private cheese: boolean;
private pepperoni: boolean;
private bacon: boolean;
constructor() {
this.size = '';
this.cheese = false;
this.pepperoni = false;
this.bacon = false;
}
setSize(size: string) {
this.size = size;
}
setCheese(cheese: boolean) {
this.cheese = cheese;
}
setPepperoni(pepperoni: boolean) {
this.pepperoni = pepperoni;
}
setBacon(bacon: boolean) {
this.bacon = bacon;
}
getDescription() {
return `Size: ${this.size}, Cheese: ${this.cheese}, Pepperoni: ${this.pepperoni}, Bacon: ${this.bacon}`;
}
}
// const order = new Pizza();
// order.setBacon(true);
// order.setCheese(true);
// order.setPepperoni(true);
// order.setSize('16');
// console.log(order.getDescription());
class PizzaBuilder {
private size: string;
private cheese: boolean;
private pepperoni: boolean;
private bacon: boolean;
setSize(size: string) {
this.size = size;
return this;
}
setCheese(cheese: boolean) {
this.cheese = cheese;
return this;
}
setPepperoni(pepperoni: boolean) {
this.pepperoni = pepperoni;
return this;
}
setBacon(bacon: boolean) {
this.bacon = bacon;
return this;
}
getDescription() {
return `Size: ${this.size}, Cheese: ${this.cheese}, Pepperoni: ${this.pepperoni}, Bacon: ${this.bacon}`;
}
}
const order = new PizzaBuilder();
const pizza = order
.setSize('16')
.setCheese(true)
.setPepperoni(true)
.setBacon(true);
console.log(pizza.getDescription());
// The Decorator pattern allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class. It's particularly useful when you need to extend the functionality of objects at runtime or when you have a large number of subclasses that would result from all possible combinations of features.
// Component interface defining the base object's behavior
interface Coffee {
cost(): number;
description(): string;
}
// Concrete Component representing a basic coffee
class SimpleCoffee implements Coffee {
cost(): number {
return 10; // Basic coffee cost
}
description(): string {
return 'Simple coffee';
}
}
// Decorator class which adds milk to the coffee
class MilkDecorator implements Coffee {
private coffee: Coffee;
constructor(coffee: Coffee) {
this.coffee = coffee;
}
cost(): number {
// Add cost of milk to the base coffee
return this.coffee.cost() + 5;
}
description(): string {
// Add description of milk to the base coffee
return this.coffee.description() + ', with Milk';
}
}
// Decorator class which adds chocolate to the coffee
class ChocolateDecorator implements Coffee {
private coffee: Coffee;
constructor(coffee: Coffee) {
this.coffee = coffee;
}
cost(): number {
// Add cost of chocolate to the base coffee
return this.coffee.cost() + 8;
}
description(): string {
// Add description of chocolate to the base coffee
return this.coffee.description() + ', with Chocolate';
}
}
// Client code
let coffee: Coffee = new SimpleCoffee(); // Creating a basic coffee
console.log(coffee.description(), '- Cost:', coffee.cost() + ' USD');
// Adding milk to the coffee
coffee = new MilkDecorator(coffee);
console.log(coffee.description(), '- Cost:', coffee.cost() + ' USD');
// Adding chocolate to the coffee
coffee = new ChocolateDecorator(coffee);
console.log(coffee.description(), '- Cost:', coffee.cost() + ' USD');
// The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together.
interface NotificationInterface {
send(subject: string, message: string): boolean;
}
class EmailNotification implements NotificationInterface {
send(subject: string, message: string): boolean {
console.log(`Sending email notification: ${subject} ${message}`);
return true;
}
}
const sendNotification = (
notification: NotificationInterface,
subject: string,
message: string
) => {
// doing some other operation
notification.send(subject, message);
};
// const emailNotification = new EmailNotification();
// sendNotification(emailNotification, 'subject', 'hi');
class ThirdPartySMSGatewayService {
sendSMS(subject: string, message: string) {
console.log(`Sending SMS notification: ${subject} ${message}`);
}
}
class ThirdPartySMSNotificationAdapter implements NotificationInterface {
private service: ThirdPartySMSGatewayService;
constructor(service: ThirdPartySMSGatewayService) {
this.service = service;
}
send(subject: string, message: string): boolean {
this.service.sendSMS(subject, message);
return true;
}
}
const smsNotification = new ThirdPartySMSNotificationAdapter(
new ThirdPartySMSGatewayService()
);
sendNotification(smsNotification, 'subject', 'hi');
// The Facade pattern is a structural design pattern that provides a simplified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use, thus hiding the complexities of the subsystem from the client.
class Subsystem1 {
operation1(): string {
return 'Subsystem1: Ready!\n';
}
// More methods and logic...
}
class Subsystem2 {
operation2(): string {
return 'Subsystem2: Go!\n';
}
// More methods and logic...
}
class Subsystem3 {
operation3(): string {
return 'Subsystem3: Start!\n';
}
// More methods and logic...
}
class Facade {
private subsystem1: Subsystem1;
private subsystem2: Subsystem2;
private subsystem3: Subsystem3;
constructor(
subsystem1: Subsystem1 = new Subsystem1(),
subsystem2: Subsystem2 = new Subsystem2(),
subsystem3: Subsystem3 = new Subsystem3()
) {
this.subsystem1 = subsystem1;
this.subsystem2 = subsystem2;
this.subsystem3 = subsystem3;
}
operation(): string {
let result = 'Facade initializes subsystems:\n';
result += this.subsystem1.operation1();
result += this.subsystem2.operation2();
result += this.subsystem3.operation3();
return result;
}
}
// Client code
const facade = new Facade();
// The client code works with complex subsystems via a simple interface provided by the Facade.
console.log(facade.operation());
// real world example
// ===================
interface User {}
interface Cart {
getItems(): any;
getTotalAmount(): number;
}
class AuthenticationService {
isAuthenticated(user: User) {
return true;
}
}
class InventoryService {
reserveItems(items: any) {}
}
class PaymentService {
processPayment(data: any) {
return 'success';
}
}
class ShippingService {
shipOrder(user: User, items: any) {}
}
class Order {
constructor(user: User, items: any, shippingDetails: any) {}
}
class OrderFacade {
private authService: AuthenticationService;
private inventoryService: InventoryService;
private paymentService: PaymentService;
private shippingService: ShippingService;
constructor() {
this.authService = new AuthenticationService();
this.inventoryService = new InventoryService();
this.paymentService = new PaymentService();
this.shippingService = new ShippingService();
}
// Example method to handle the entire order process
placeOrder(user: User, cart: Cart): Order {
// Authenticate the user
if (!this.authService.isAuthenticated(user)) {
throw new Error('User authentication failed.');
}
// Reserve items from inventory
this.inventoryService.reserveItems(cart.getItems());
// Process payment
const paymentStatus = this.paymentService.processPayment(
cart.getTotalAmount()
);
if (paymentStatus !== 'success') {
throw new Error('Payment processing failed.');
}
// Ship the order
const shippingDetails = this.shippingService.shipOrder(
user,
cart.getItems()
);
// Create and return order object
return new Order(user, cart.getItems(), shippingDetails);
}
}
const getUser = (): User => {
return { id: 1, user: 'Hadi' };
};
const getCart = (): Cart => {
return {
getItems() {
return [
{ id: 1, name: 'Banana' },
{ id: 2, name: 'Egg' },
];
},
getTotalAmount() {
return 100;
},
};
};
const orderFacade = new OrderFacade();
try {
const user = getUser(); // Retrieve user information
const cart = getCart(); // Retrieve shopping cart contents
const order = orderFacade.placeOrder(user, cart);
console.log('Order placed successfully:', order);
} catch (error) {
console.error('Failed to place order:', error.message);
}
// The Factory Design Pattern is a creational pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This pattern is particularly useful when a system needs to introduce new classes without modifying existing code that uses the old classes.
interface LoggerStrategyInterface {
log(data: string): void;
}
class Logger {
public strategy: LoggerStrategyInterface;
constructor(strategy: LoggerStrategyInterface) {
this.strategy = strategy;
}
log(data: string) {
this.strategy.log(data);
}
}
class ConsoleLogStrategy implements LoggerStrategyInterface {
log(data: string) {
console.log(`via console log: ${data}`);
}
}
class DbLogStrategy implements LoggerStrategyInterface {
log(data: string) {
console.log(`via db log: ${data}`);
}
}
// const logger = new Logger(new DbLogStrategy());
// logger.log(`some message`);
class LoggerFactory {
public static log(data: string) {
const logger = new Logger(new DbLogStrategy());
logger.log(data);
}
}
LoggerFactory.log(`some message`);
// another example
// ==============================================
interface Vehicle {
getType(): string;
}
class Car implements Vehicle {
getType(): string {
return 'Car';
}
// Additional methods specific to Car can be added here
}
class Motorcycle implements Vehicle {
getType(): string {
return 'Motorcycle';
}
// Additional methods specific to Motorcycle can be added here
}
class VehicleFactory {
static createVehicle(type: 'car' | 'motorcycle'): Vehicle {
switch (type) {
case 'car':
return new Car();
case 'motorcycle':
return new Motorcycle();
default:
throw new Error(`Vehicle type ${type} not supported.`);
}
}
}
// usage
try {
const car = VehicleFactory.createVehicle('car');
console.log(`Created vehicle type: ${car.getType()}`);
const motorcycle = VehicleFactory.createVehicle('motorcycle');
console.log(`Created vehicle type: ${motorcycle.getType()}`);
} catch (error) {
console.error(error);
}
class Event {
static events = [];
static listen(name, callback) {
if (!Event.events[name]) {
Event.events[name] = [];
}
Event.events[name].push(callback);
}
static listeners(eventNames = [], callback) {
if (eventNames.length === 0) {
return;
}
eventNames.forEach((name) => {
Event.listen(name, callback);
});
}
static dispatch(name, arg) {
if (Event.events[name]) {
Event.events[name].forEach((callback) => {
if (arg) {
callback.call(Event, name, arg);
} else {
callback.call(Event, name);
}
});
}
}
}
// Usage
// ==============================================
Event.listeners(['login', 'logout'], (eventName, value) => {
console.log(`${eventName} event triggered!`);
console.log(value);
});
Event.dispatch('login', 'hadi');
Event.dispatch('logout', {
c: 3,
});
Event.listen('purchase', (name, orderNumber) => {
console.log(`${orderNumber} has been purchased!`);
});
setTimeout(() => {
Event.dispatch('purchase', 'ABC-123');
}, 5000);
class Singleton {
private static instance: Singleton;
private constructor() {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
public someMethod() {
console.log('Some method of the Singleton class');
}
}
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2);
instance1.someMethod();
// nodejs
/*
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
someMethod() {
console.log("Some method of the Singleton class");
}
}
// Usage
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // Output: true, since both instances are the same
instance1.someMethod(); // Output: "Some method of the Singleton class"
*/
// The Strategy pattern is a behavioral design pattern that enables selecting an algorithm's runtime from a family of algorithms. It defines a family of algorithms, encapsulates each one of them, and makes them interchangeable within that family. The strategy pattern lets the algorithm vary independently from clients that use it.
// 1. just console log
// 2. database log
// 3. may be file based log
interface LoggerStrategyInterface {
log(data: string): void;
}
class Logger {
public strategy: LoggerStrategyInterface;
constructor(strategy: LoggerStrategyInterface) {
this.strategy = strategy;
}
log(data: string) {
this.strategy.log(data);
}
}
class ConsoleLogStrategy implements LoggerStrategyInterface {
log(data: string) {
console.log(`via console log: ${data}`);
}
}
class DbLogStrategy implements LoggerStrategyInterface {
log(data: string) {
console.log(`via db log: ${data}`);
}
}
const logger = new Logger(new DbLogStrategy());
logger.log(`some message`);
// the Template Method is a behavioral design pattern that allows you to define the skeleton of an algorithm in a base class while letting subclasses provide the implementation details for certain steps of the algorithm. It promotes code reuse and enables variations of the algorithm to be easily implemented without modifying the overall structure.
abstract class AbstractClass {
// The template method that defines the algorithm structure
public templateMethod(): void {
this.stepOne();
this.stepTwo();
this.stepThree();
}
// Abstract methods to be implemented by subclasses
protected abstract stepOne(): void;
protected abstract stepTwo(): void;
// Optional hook method that subclasses can override
protected stepThree(): void {
// Default implementation
}
}
class ConcreteClass extends AbstractClass {
protected stepOne(): void {
console.log('ConcreteClass: Step One');
}
protected stepTwo(): void {
console.log('ConcreteClass: Step Two');
}
}
// real world example
// ====================
abstract class Transformer {
protected responseJson(array: any): string {
return JSON.stringify(array);
}
abstract transform(item: any): string;
}
class UserTransformer extends Transformer {
transform(item: any): string {
return this.responseJson({ name: item['name'] });
}
}
class ProductTransformer extends Transformer {
transform(item: any): string {
return this.responseJson({ name: item['product_name'] });
}
}
const userTransformer = new UserTransformer();
console.log(userTransformer.transform({ id: 1, name: 'im4aLL' }));
const productTransformer = new ProductTransformer();
console.log(
productTransformer.transform({ id: 1, product_name: 'Sample product' })
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment