Structural patterns help compose objects and classes into larger structures while keeping these structures flexible and efficient. When building my MCP server and IoT platform, I constantly needed to integrate external APIs, add features to existing code, and simplify complex subsystems.
This guide covers four structural patterns I use regularly:
Adapter Pattern - Making incompatible interfaces work together
Decorator Pattern - Adding behavior to objects dynamically
Facade Pattern - Providing simple interfaces to complex systems
Proxy Pattern - Controlling access to objects
Adapter Pattern
Allows incompatible interfaces to work together by wrapping an object with a compatible interface.
The Problem I Had
In my Agentic LLM project, I needed to integrate DuckDuckGo's search API, but its response format didn't match my internal interfaces:
My Solution: Adapter Pattern
I created an adapter to convert between interfaces:
Multiple Adapters Example
Later, I added Google Custom Search without changing existing code:
Decorator Pattern
Adds new functionality to objects dynamically without modifying their structure.
The Problem I Had
In my MCP server, I needed to add logging, timing, and error handling to tools without modifying each tool class:
My Solution: Decorator Pattern
I created decorators for cross-cutting concerns:
TypeScript Decorators
TypeScript also has native decorator syntax (experimental):
Facade Pattern
Provides a simplified interface to a complex subsystem.
The Problem I Had
My IoT platform had complex interactions between MongoDB, LLM service, and data formatting:
My Solution: Facade Pattern
I created a simple facade over the complex subsystem:
Real-World Facade: MCP Server Tools
Here's how I used facade in my MCP server to simplify security scanning:
Proxy Pattern
Provides a surrogate or placeholder for another object to control access to it.
The Problem I Had
In my IoT platform, sensor data queries to the LLM were expensive and slow:
My Solution: Caching Proxy
I created a proxy to cache expensive operations:
Virtual Proxy (Lazy Loading)
I also use proxies for lazy initialization of expensive resources:
Protection Proxy (Access Control)
In my MCP server, I use proxies to control access to sensitive operations:
Combining Structural Patterns
Here's a real example combining multiple structural patterns from my MCP server:
When to Use Each Pattern
Adapter Pattern
β Use when:
Integrating third-party libraries with incompatible interfaces
Working with legacy code
Need to switch between alternative implementations
β Avoid when:
You control both interfaces (just make them compatible)
Conversion adds no value
Decorator Pattern
β Use when:
Adding responsibilities to objects dynamically
Need composable enhancements (logging, caching, timing)
Want to avoid subclass explosion
β Avoid when:
Simple inheritance is sufficient
Behavior should be fixed at compile time
Facade Pattern
β Use when:
Subsystem is complex with many interdependent classes
Want to provide simple interface to complex functionality
Need to decouple clients from subsystem implementation
β Avoid when:
System is already simple
Clients need fine-grained control
Proxy Pattern
β Use when:
Need lazy initialization of expensive objects
Want to add access control, caching, or logging
Objects are remote or need controlled access
β Avoid when:
Direct access is simpler and sufficient
Indirection adds unnecessary complexity
Conclusion
Structural patterns help organize code effectively:
Adapter: Bridges incompatible interfaces
Decorator: Adds behavior flexibly
Facade: Simplifies complex systems
Proxy: Controls object access
I use these patterns constantly in my TypeScript projects to integrate APIs, add features cleanly, and manage complexity.
// β BAD: Directly using external API format throughout codebase
interface DDGSearchResult {
Text: string; // Capital T
FirstURL: string; // Capital F, U
Icon: {
URL: string;
Height: number;
Width: number;
};
}
class AgentService {
async search(query: string): Promise<void> {
const response = await fetch(`https://api.duckduckgo.com/?q=${query}&format=json`);
const data = await response.json();
// My code is coupled to DuckDuckGo's format
data.RelatedTopics.forEach((topic: DDGSearchResult) => {
console.log(topic.Text); // Using their naming
console.log(topic.FirstURL); // Their capitalization
});
}
}
// Problem: If I switch search providers, I'd need to update code everywhere!
// β GOOD: Adapter pattern
// My application's interface
interface SearchResult {
title: string;
url: string;
snippet: string;
}
interface SearchProvider {
search(query: string): Promise<SearchResult[]>;
}
// DuckDuckGo's actual response format
interface DDGResponse {
RelatedTopics: DDGTopic[];
}
interface DDGTopic {
Text: string;
FirstURL: string;
Icon?: {
URL: string;
};
}
// Adapter for DuckDuckGo
class DuckDuckGoAdapter implements SearchProvider {
async search(query: string): Promise<SearchResult[]> {
const response = await fetch(
`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json`
);
const data: DDGResponse = await response.json();
// Convert DuckDuckGo format to my application's format
return data.RelatedTopics
.filter(topic => topic.FirstURL && topic.Text)
.map(topic => this.adaptTopic(topic));
}
private adaptTopic(topic: DDGTopic): SearchResult {
return {
title: this.extractTitle(topic.Text),
url: topic.FirstURL,
snippet: topic.Text
};
}
private extractTitle(text: string): string {
// Extract title from text (usually before the first '-')
const parts = text.split(' - ');
return parts[0] || text;
}
}
// Clean client code that doesn't know about DuckDuckGo
class AgentService {
constructor(private searchProvider: SearchProvider) {}
async performSearch(query: string): Promise<SearchResult[]> {
// Works with any search provider!
return await this.searchProvider.search(query);
}
}
// Usage
const ddgAdapter = new DuckDuckGoAdapter();
const agent = new AgentService(ddgAdapter);
const results = await agent.performSearch('TypeScript design patterns');
results.forEach(result => {
console.log(result.title); // My format
console.log(result.url); // My naming
});