# Structural Patterns

Course: [Low-Level Design](https://www.devtutorials.ai/learn/system-design/lld) · Section: LLD Foundations
Source: https://www.devtutorials.ai/learn/system-design/lld/foundations/structural-patterns

> Learn adapter, decorator, facade and proxy with QuickBite examples, then adapt two map services to one interface and add a caching decorator.

When you travel to another country, your phone charger may not fit the wall socket. You do not need to change the charger or the wall socket. Instead, you use a small **adapter** between them.

Classes face a similar problem. QuickBite uses services and libraries from different companies. These services often use different method names and interfaces. **Structural patterns** provide common ways to connect classes without modifying their existing code.

**In simple words:** structural patterns are adapters, wrappers, and middle layers that help different classes work together smoothly.

## What You Will Learn

In this lesson, you will learn four structural patterns:

1. **Adapter:** change one interface so that it matches another.
2. **Decorator:** add new behavior by wrapping an object without changing its code.
3. **Facade:** provide one simple interface to hide a group of classes.
4. **Proxy:** act as a replacement for an object and control access to it.

All four patterns use the same idea from the OOP and SOLID lessons: code works with an **interface** instead of directly using a concrete class. This makes it possible to place another object in between when needed.

## Adapter: Make It Fit

QuickBite's code expects distances in kilometres through a `DistanceService` interface. However, an older map library returns distances in metres and uses a different method name.

An **adapter** is a small class that makes these two interfaces work together. It implements the interface that QuickBite expects and internally calls the old library.

### The Example

`OldMapsAdapter` implements the `DistanceService` interface that QuickBite expects. Inside its `km()` method, it calls the old library's `distanceInMeters()` and divides by 1000.

<LldDiagram caption="The adapter implements the new interface and holds the old API inside" code={"classDiagram\n  class DistanceService {\n    <<interface>>\n    +km(fromArea, toArea) number\n  }\n  class OldMapsAdapter {\n    -api: OldMapsApi\n    +km(fromArea, toArea) number\n  }\n  class OldMapsApi {\n    +distanceInMeters(a, b) number\n  }\n  DistanceService <|.. OldMapsAdapter\n  OldMapsAdapter --> OldMapsApi : calls"} />

Follow one call from Indiranagar to Koramangala:

The code behind the animation:

```ts runnable title="An adapter makes an old API fit the new interface"
// The interface QuickBite's code expects.
interface DistanceService {
  km(fromArea: string, toArea: string): number;
}

// An old library we cannot change. It speaks metres, with a different name.
class OldMapsApi {
  distanceInMeters(a: string, b: string): number {
    return a === b ? 0 : 4200;
  }
}

// The adapter: implements the new interface, uses the old API inside.
class OldMapsAdapter implements DistanceService {
  constructor(private readonly api: OldMapsApi) {}
  km(fromArea: string, toArea: string): number {
    return this.api.distanceInMeters(fromArea, toArea) / 1000;
  }
}

const maps: DistanceService = new OldMapsAdapter(new OldMapsApi());
console.log(maps.km("Indiranagar", "Koramangala"));
```

Expected output:

```text
4.2
```

The old API answers 4200 metres. The adapter turns that into 4.2 kilometres.

### Why It Helps

Neither side needs to be changed. If QuickBite later switches to a different map provider, you only need to create a new adapter.

Without an adapter, every caller would need to convert metres into kilometres on its own. Over time, someone could forget to do the conversion correctly.

## Decorator: Wrap to Add

A **decorator** wraps around an object and adds extra behavior to it, similar to wrapping a gift. The decorator uses the **same interface** as the original object, so callers cannot tell whether they are using the original object or the decorated one.

### The Example

Here, `LoggedNotifier` adds a log line to any notifier, without editing `SmsNotifier`. It has the same `send()` method and keeps the real notifier inside, in a field called `inner`. Step through one message:

Here is the same idea in code:

```ts runnable title="A decorator adds a feature without changing the class"
interface Notifier {
  send(to: string, text: string): string;
}

class SmsNotifier implements Notifier {
  send(to: string, text: string): string { return `SMS to ${to}: ${text}`; }
}

// Same interface. Wraps another Notifier. Adds one thing: a log line.
class LoggedNotifier implements Notifier {
  readonly log: string[] = [];
  constructor(private readonly inner: Notifier) {}
  send(to: string, text: string): string {
    this.log.push(`sending to ${to}`);
    return this.inner.send(to, text);
  }
}

const logged = new LoggedNotifier(new SmsNotifier());
console.log(logged.send("98450 12345", "Order placed"));
console.log(logged.log);
```

Expected output:

```text
SMS to 98450 12345: Order placed
[
  "sending to 98450 12345"
]
```

The caller gets "SMS to 98450 12345: Order placed", and the wrapper's log holds one line: "sending to 98450 12345".

### Stacking Decorators

Because each decorator has the same interface, you can wrap a wrapper. Each layer adds one thing. Here, `Shout` turns the text into capitals and `WithSignature` adds " - QuickBite". Watch the message pass through `WithSignature(Shout(Sms))`:

The code behind the animation:

```ts runnable title="Decorators stack, like layers of wrapping"
interface Notifier {
  send(text: string): string;
}
class Sms implements Notifier {
  send(text: string): string { return `SMS: ${text}`; }
}
class Shout implements Notifier {
  constructor(private readonly inner: Notifier) {}
  send(text: string): string { return this.inner.send(text.toUpperCase()); }
}
class WithSignature implements Notifier {
  constructor(private readonly inner: Notifier) {}
  send(text: string): string { return this.inner.send(`${text} - QuickBite`); }
}

const plain = new Sms();
const fancy = new WithSignature(new Shout(new Sms()));
console.log(plain.send("rider is near"));
console.log(fancy.send("rider is near"));
```

Expected output:

```text
SMS: rider is near
SMS: RIDER IS NEAR - QUICKBITE
```

| Notifier | Result for "rider is near" |
|---|---|
| `Sms` alone | SMS: rider is near |
| `WithSignature(Shout(Sms))` | SMS: RIDER IS NEAR - QUICKBITE |

The innermost `Sms` object was never modified. The two outer layers simply added uppercase text and a signature.

**Easy way to remember the difference:** an **adapter** changes the interface (a different shape). A **decorator** keeps the same interface and adds extra behavior. A **proxy** also keeps the same interface, but controls access to the object.

## Facade: One Front Desk

When you stay at a hotel, you do not contact housekeeping, the kitchen, and the laundry separately. Instead, you call the front desk.

A **facade** works the same way in programming. It provides one simple method that callers use, while it communicates with multiple classes behind the scenes.

### The Example

The checkout page only calls `placeOrder()`. It does not need to know about four separate classes: `Menu`, `Payments`, `Kitchen` and `Riders`. Step through one dosa order:

Here is the same idea in code:

```ts runnable title="A facade hides many steps behind one method"
class Menu { priceOf(dish: string): number { return dish === "Dosa" ? 120 : 80; } }
class Payments { charge(amount: number): string { return `charged ${amount}`; } }
class Kitchen { cook(dish: string): string { return `cooking ${dish}`; } }
class Riders { assign(): string { return "rider Ravi assigned"; } }

// The facade: one simple method for the checkout page.
class OrderFacade {
  private readonly menu = new Menu();
  private readonly payments = new Payments();
  private readonly kitchen = new Kitchen();
  private readonly riders = new Riders();

  placeOrder(dish: string): string[] {
    const price = this.menu.priceOf(dish) + 30;
    return [this.payments.charge(price), this.kitchen.cook(dish), this.riders.assign()];
  }
}

for (const step of new OrderFacade().placeOrder("Dosa")) console.log(step);
```

Expected output:

```text
charged 150
cooking Dosa
rider Ravi assigned
```

The facade returns three lines: "charged 150", "cooking Dosa" and "rider Ravi assigned". The 150 is the dosa price of 120 plus the delivery fee of 30.

### Why It Helps

If the order steps change, only the facade changes. The checkout page, the mobile app and the admin tool all keep calling `placeOrder()`.

## Proxy: A Guard at the Door

A **proxy** stands in front of one object and has the same interface. It decides whether, or when, to pass a call through.

### Checking Access

`AdminOnlyReports` has the same `dailySales()` method as the real `SalesReports`. Before passing the call on, it checks the caller's role. Watch an admin and a guest ask for the same report:

The code behind the animation:

```ts runnable title="A proxy stands in front and checks who is asking"
interface Reports {
  dailySales(): string;
}

class SalesReports implements Reports {
  dailySales(): string { return "Today: 1,240 orders"; }
}

// Same interface. Decides whether to let the call through.
class AdminOnlyReports implements Reports {
  constructor(private readonly real: Reports, private readonly role: string) {}
  dailySales(): string {
    if (this.role !== "admin") return "Access denied";
    return this.real.dailySales();
  }
}

const real = new SalesReports();
console.log(new AdminOnlyReports(real, "admin").dailySales());
console.log(new AdminOnlyReports(real, "guest").dailySales());
```

Expected output:

```text
Today: 1,240 orders
Access denied
```

The admin sees "Today: 1,240 orders". The guest sees "Access denied".

### Creating Only When Needed

A proxy can also delay creating something expensive until it is really used. This is called a **lazy** proxy.

`LazyMenuImages` stands in for `MenuImages`, which loads 300 images when it is created. The proxy waits until someone asks for an image. Step through a page that opens and then asks for the first image twice:

Here is the same idea in code:

```ts runnable title="A lazy proxy: create the expensive object only when needed"
class MenuImages {
  constructor() { console.log("loading 300 images..."); }
  first(): string { return "dosa.jpg"; }
}

class LazyMenuImages {
  private real: MenuImages | null = null;
  first(): string {
    if (this.real === null) this.real = new MenuImages(); // made on first use
    return this.real.first();
  }
}

const images = new LazyMenuImages();
console.log("page opened");
console.log(images.first());
console.log(images.first());
```

Expected output:

```text
page opened
loading 300 images...
dosa.jpg
dosa.jpg
```

The order of events is: "page opened", then "loading 300 images...", then "dosa.jpg" twice. "loading 300 images..." happens only once, and only when the first image is requested.

The chart puts all four patterns side by side. Look at what sits in the middle and whether the caller sees the same interface as the object behind it:

Add a wrapper only when you need it, because each one is another class to understand. Use an adapter, decorator, facade or proxy for a real need: a different API, extra behavior, hiding steps or controlling access.

## Lab: Two Map Services and a Cache

QuickBite uses two different map services. One service returns distances in metres, while the other returns a route object with distances in kilometres. Both services are already provided in the code. Do not modify them.

Keep `adaptOldMaps`, `adaptGeo`, and `withCache` unchanged because the tests depend on them.

1. Create `OldMapsAdapter` so that the old API works as a `DistanceService` and returns distances in kilometres.
2. Create `GeoAdapter` for the geo client.
3. Create `CachedDistance`, a decorator that stores the result for each trip so that the real service is called only once for the same trip.

## Predict the Output

Here is a decorator example in C#. Read the code and try to predict the output before running it.

<CsPredict id="decorator-1" code={"INotifier n = new Loud(new Signed(new Sms()));\nConsole.WriteLine(n.Send(\"hi\"));\n\ninterface INotifier { string Send(string t); }\nclass Sms : INotifier { public string Send(string t) => \"SMS: \" + t; }\nclass Signed : INotifier\n{\n    private readonly INotifier inner;\n    public Signed(INotifier i) => inner = i;\n    public string Send(string t) => inner.Send(t + \" - QB\");\n}\nclass Loud : INotifier\n{\n    private readonly INotifier inner;\n    public Loud(INotifier i) => inner = i;\n    public string Send(string t) => inner.Send(t.ToUpper());\n}"} expected={"SMS: HI - QB"} why="Loud is the outermost layer, so it runs first and converts the text to HI. Signed then adds - QB to the text. Finally, Sms adds the SMS: prefix. The signature remains lowercase because it was added after the uppercase conversion." />

## Common Mistakes

| Mistake | Wrong | Correct | Why it happens |
|---|---|---|---|
| Converting in every caller | `api.distanceInMeters(...) / 1000` in multiple files. | Create one adapter that handles the conversion. | Adding the conversion directly often seems like the fastest solution. |
| Editing a class to add a feature | Adding caching logic inside `GeoClient`. | Use a `CachedDistance` decorator around it. | Modifying an existing class can feel easier than creating a wrapper. |
| Decorator with a different interface | The wrapper uses `sendWithLog()` instead of `send()`. | The wrapper should keep the same `send()` method. | Renaming methods may seem clearer at first. |
| A facade that does too much | `OrderFacade` contains fifty different methods. | Keep only a few simple methods and split responsibilities when needed. | New features often get added to the facade over time. |
| Incomplete cache key | The key uses only the starting location. | The key should include all values that affect the result. | Early tests may use only one route, hiding the problem. |

## Practice

## What to Remember

- **Adapter:** changes the interface so a class can work with the interface you need.
- **Decorator:** keeps the same interface and adds extra behavior. Multiple decorators can be stacked together.
- **Facade:** provides one simple entry point for working with multiple classes.
- **Proxy:** keeps the same interface and controls access to an object or delays its creation.
- All four patterns work because code interacts with interfaces instead of directly depending on concrete classes.

## Further Reading

These are the sources used to check this lesson:

- [Refactoring.Guru: Adapter](https://refactoring.guru/design-patterns/adapter): the adapter pattern with diagrams.
- [Refactoring.Guru: Decorator](https://refactoring.guru/design-patterns/decorator): wrapping to add behaviour.
- [Refactoring.Guru: Facade](https://refactoring.guru/design-patterns/facade): one front door for a subsystem.
- [Refactoring.Guru: Proxy](https://refactoring.guru/design-patterns/proxy): access control and lazy loading.

Next: **Behavioural Patterns**. You will learn how objects share work: strategy, observer, state, command and chain of responsibility.
