TypeScript Composite Design Pattern

Ibrahim sengun
3 min readJan 5, 2024

--

What is composite design pattern?

The composite design pattern is a structural design pattern also known as the object tree design pattern. It composes objects to form a tree structure and provides the ability to use individual objects within the tree structure.

There are several terminologies in the composite design pattern. These are:

  • Component Interface: It defines the common properties for the tree structure.
  • Leaf: It’s the lowest-level object; it doesn’t have any sub-elements. Hence, it performs tasks without delegating to another object.
  • Composite: It’s the object that contains various types of subclasses or leaves. It doesn’t need to know the dependencies of its children; it works via the component interface.
  • Client: It’s the application or function that communicates with the component interface.

When should the composite design pattern be used?

The composite design pattern can be used when the system’s structure mimics or operates like a tree. Its implementation becomes more straightforward when there is an iteration that involves encapsulating a complex system.

It can also be employed when there is a necessity to handle both simple and complex objects uniformly across the entire system.

How to implement composite design pattern in TypeScript

Let’s try to apply the composite design pattern to TypeScript. Let’s imagine we are dealing with a system for product price management. Our task is to calculate the price of a given product or list of products. However, the catch is that products can be in different types, so there is no common structure among products. This makes our task difficult.

If we try to handle the product prices manually and create classes for each type of product group, we would be overwhelmed. There could be a product group with just one element or one with twenty elements. Trying to apply a specific design to each of them would be meaningless. That’s why we attempt to employ the composite design pattern.

We extract the common property, ‘price,’ from all of them and define it in a higher-level class, the component interface. Then, we create a container for the product list and leaf for the product types and single products. We structure our system like a tree with a common property of price.

Now, with a design like this, it wouldn’t matter how much or how different our products need to work because we don’t need to know their types or dependencies. We’re only interested in their common property: the price.

Composite desing pattern diagram

Composite desing pattern diagram

Composite desing pattern code

// Composite interface
interface ProductComponent {
getPrice(): number;
}

// Leaf
class Product implements ProductComponent {
private price: number;

constructor(price: number) {
this.price = price;
}

getPrice(): number {
return this.price;
}
}

// Leaf
class DiscountedProduct implements ProductComponent {
private price: number;
private discount: number;

constructor(price: number, discount: number) {
this.price = price;
this.discount = discount
}

getPrice(): number {
return this.price - this.discount;
}
}

// Composite
class ProductContainer implements ProductComponent {
private children: ProductComponent[] = [];

add(component: ProductComponent): void {
this.children.push(component);
}

getPrice(): number {
return this.children.reduce((totalPrice, child) => totalPrice + child.getPrice(), 0);
}
}

// Client
const laptop = new Product(300);
const phone = new DiscountedProduct(200, 50);

const smallBox = new ProductContainer();
smallBox.add(laptop);

// Output: Total price of small Box: 300
console.log("Total price of small Box:", smallBox.getPrice());

const bigBox = new ProductContainer();
bigBox.add(phone);
bigBox.add(smallBox);

// Output: Total price of big Box: 450
console.log("Total price of big Box:", bigBox.getPrice());

--

--