Advanced JavaScript Design Patterns: Part 4

Advanced JavaScript Design Patterns: Part 4

The Facade Pattern

ยท

4 min read

The facade design pattern is a structural pattern that provides a simplified interface to a complex system of classes, objects or libraries. It acts as a wrapper that sits between the client and the complex system and presents a simplified and easy-to-use interface to the client. This pattern is commonly used in software development to make it easier for clients to interact with large, complex systems.

Thinking of a real-world example of a facade design pattern, it could be a web-based e-commerce platform. The platform has many different components that need to interact with each other, such as the product catalog, inventory management, order processing, and shipping. Instead of having the client interact directly with each of these components, a facade class is created that acts as a single point of entry for the client.

The facade class could have methods such as getProduct(), addToCart(), placeOrder(), and trackShipment() that the client can call. These methods internally call the appropriate methods on the other components of the system, such as the product catalog, inventory management, and shipping.

This simplified interface provided by the facade class makes it easier for the client to interact with the system, as they only need to be concerned with the methods provided by the facade. Additionally, it provides a layer of abstraction that allows the internal implementation of the system to change without affecting the client.

type Product = {
  id: number;
  name: string;
  price: number;
}

type Order = {
  orderId: number;
  product: Product;
  quantity: number;
  totalPrice: number;
}

type Shipment = {
  trackingNumber: string;
  carrier: string;
  status: string;
}


class ProductCatalog {
  getProduct(productId: number): Product {
    // code to retrieve product from the catalog
    return { id: productId, name: 'Product ' + productId, price: 100 };
}

}

class InventoryManagement {
  private inventory: Map<number, number> = new Map<number, number>()
  .set(123, 2)
    .set(124, 0)
    .set(125, 5);
;

  checkInventory(productId: number): boolean {
    const productQuantity = this.inventory.get(productId);
    if(productQuantity){
      return productQuantity > 0;
    }
    return false;
  }

  updateInventory(productId: number, quantity: number): void {
    if (quantity > 0) {
      this.inventory.set(productId, quantity);
    }
  }
}


class OrderProcessing {
  private orders: Order[] = [];

  placeOrder(product: Product, quantity: number): Order {
    // code to process the order
    const order: Order = { orderId: this.orders.length + 1, product, quantity, totalPrice: quantity * product.price };
    this.orders.push(order);
    return order;
  }
}


class Shipping {
  private shipments: Map<number, Shipment> = new Map<number, Shipment>()
    .set(1, { trackingNumber: 'ABC123', carrier: 'UPS', status: 'In Transit' })
    .set(2, { trackingNumber: 'DEF456', carrier: 'FedEx', status: 'Delivered' });

  trackShipment(orderId: number): Shipment {
    // code to track the shipment
    const shipment = this.shipments.get(orderId);
    if (shipment) {
      return shipment;
    } else {
      throw new Error(`Shipment for orderId ${orderId} not found`);
    }
  }
}



class ECommerceFacade {
  private productCatalog: ProductCatalog;
  private inventoryManagement: InventoryManagement;
  private orderProcessing: OrderProcessing;
  private shipping: Shipping;

  constructor() {
    this.productCatalog = new ProductCatalog();
    this.inventoryManagement = new InventoryManagement();
    this.orderProcessing = new OrderProcessing();
    this.shipping = new Shipping();
  }

  getProduct(productId: number): Product {
    return this.productCatalog.getProduct(productId);
  }

  addToCart(productId: number, quantity: number): boolean {
    const product = this.productCatalog.getProduct(productId);
    if (product) {
      if (this.inventoryManagement.checkInventory(productId)) {
        this.inventoryManagement.updateInventory(productId, quantity);
        return true;
      }else{
        this.inventoryManagement.updateInventory(productId, 0);
        return false;
      }
    }else{
      throw new Error(`Product with id ${productId} not found in the catalog`);
    }
  }

    placeOrder(productId: number, quantity: number): Order {
    const product = this.productCatalog.getProduct(productId);
    if (product) {
      return this.orderProcessing.placeOrder(product, quantity);
    }else{
      throw new Error(`Product with id ${productId} not found in the catalog`);
    }
  }

  trackShipment(orderId: number): Shipment {
    return this.shipping.trackShipment(orderId);
  }
}

const ecommerceFacade = new ECommerceFacade();

const product = ecommerceFacade.getProduct(123);
console.log(product);

const isAddedToCart = ecommerceFacade.addToCart(123, 2);
console.log(isAddedToCart);

const order = ecommerceFacade.placeOrder(123, 2);
console.log(order);

console.log(order.orderId , "order.orderId")
const shipment = ecommerceFacade.trackShipment(order.orderId);
console.log(shipment);

In this example, the ECommerceFacade class acts as a facade for the ProductCatalog, InventoryManagement, OrderProcessing, and Shipping classes. The client can interact with the system by calling the methods on the facade, such as getProduct(), addToCart(), placeOrder(), and trackShipment(). The facade class takes care of calling the appropriate methods on the other classes, and provides a simplified interface for the client.

Conclusion

In this article, we have discussed the Facade design pattern, its implementation and how it can be used in a real-world application for an e-commerce system using TypeScript.

If you have any questions, You can reach out to me on:

Twitter

LinkedIn

Did you find this article valuable?

Support Jay Desai by becoming a sponsor. Any amount is appreciated!

ย