feat(skills): add design-patterns analyzer skill

Comprehensive GoF design patterns analyzer with stack-aware suggestions.

**Features**:
- Detects 23 GoF patterns (Creational, Structural, Behavioral)
- Stack detection (React, Angular, NestJS, Vue, Express, RxJS, Redux, ORMs)
- Code smell detection with pattern suggestions
- Quality evaluation (5 criteria scoring)
- Prefers stack-native alternatives (e.g., React Context over Singleton)

**Structure**:
- 9 files: SKILL.md + reference docs + detection rules + evaluation checklists
- 3 operating modes: Detection, Suggestion, Evaluation
- Pattern-specific documentation for all 23 GoF patterns

**Documentation**:
- Added comprehensive example in guide section 5.4
- Updated examples/README.md with skill entry
- Updated template count: 65 → 66

**Use cases**:
- Analyze existing patterns in codebase
- Suggest refactoring with stack-native patterns
- Evaluate pattern implementation quality

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Florian BRUNIAUX 2026-01-21 13:46:20 +01:00
parent 936f8dd564
commit fcc6f2dba3
12 changed files with 5842 additions and 3 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,562 @@
# Creational Design Patterns
Patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.
## Singleton
### Definition
Ensures a class has only one instance and provides a global point of access to it.
### When to Use
- [x] Exactly one instance of a class is needed (configuration, logging, database connection)
- [x] Controlled access to a single object is required
- [x] The instance should be extensible by subclassing
**Warning**: Often overused. Consider dependency injection or context-based alternatives first.
### TypeScript Signature
```typescript
class Singleton {
private static instance: Singleton;
private constructor() {
// Private constructor prevents instantiation
}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
public someMethod(): void {
// Business logic
}
}
// Usage
const instance = Singleton.getInstance();
```
### Stack-Native Alternatives
**React**:
```typescript
// Instead of Singleton, use Context
const ConfigContext = createContext<Config>(defaultConfig);
export const ConfigProvider = ({ children }: Props) => {
const [config] = useState(() => loadConfig());
return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;
};
```
**Angular**:
```typescript
// Injectable service (singleton by default)
@Injectable({ providedIn: 'root' })
export class ConfigService {
// Automatically singleton via DI
}
```
**NestJS**:
```typescript
@Injectable() // Default scope is SINGLETON
export class AppService {}
```
### Detection Markers
- `private constructor`
- `static getInstance()` method
- `private static instance` field
- Lazy initialization check: `if (!instance)`
### Code Smells It Fixes
- **Global state access**: Provides controlled access instead of scattered global variables
- **Multiple instances of shared resource**: Ensures single database connection, config object, etc.
### Common Mistakes
- **Hard to test**: Static methods and global state make unit testing difficult
- *Solution*: Use dependency injection instead, or provide `resetInstance()` for tests
- **Thread-safety issues**: (Less relevant in JavaScript's single-threaded model, but important for Node.js workers)
- **Hidden dependencies**: Classes using `getInstance()` have hidden coupling
- **Violates Single Responsibility**: Often manages both instance creation and business logic
### Evaluation Criteria
- **Testability**: 3/10 (hard to mock, global state)
- **Thread-safety**: 7/10 (less critical in JS)
- **Extensibility**: 5/10 (subclassing is complex)
---
## Factory Method
### Definition
Defines an interface for creating an object, but lets subclasses decide which class to instantiate.
### When to Use
- [x] Class cannot anticipate the type of objects it needs to create
- [x] Class wants its subclasses to specify the objects it creates
- [x] Centralize object creation logic to avoid duplication
- [x] Need to decouple object creation from usage
### TypeScript Signature
```typescript
// Product interface
interface Product {
operation(): string;
}
// Concrete products
class ConcreteProductA implements Product {
operation(): string {
return 'Product A';
}
}
class ConcreteProductB implements Product {
operation(): string {
return 'Product B';
}
}
// Creator (Factory)
abstract class Creator {
// Factory method
abstract createProduct(): Product;
// Business logic using the product
someOperation(): string {
const product = this.createProduct();
return `Creator: ${product.operation()}`;
}
}
// Concrete creators
class CreatorA extends Creator {
createProduct(): Product {
return new ConcreteProductA();
}
}
class CreatorB extends Creator {
createProduct(): Product {
return new ConcreteProductB();
}
}
// Usage
const creator: Creator = new CreatorA();
console.log(creator.someOperation());
```
### Modern TypeScript Alternative
```typescript
// Simpler approach without inheritance
type ProductType = 'A' | 'B';
function createProduct(type: ProductType): Product {
switch (type) {
case 'A': return new ConcreteProductA();
case 'B': return new ConcreteProductB();
}
}
```
### Detection Markers
- Method named `create*()` returning interface/abstract class
- `abstract createProduct()` in base class
- Subclasses override factory method
- `switch` or `if-else` on type/kind parameter
### Code Smells It Fixes
- **Tight coupling to concrete classes**: Client code depends on interface, not implementation
- **Duplication of instantiation logic**: Centralized in factory method
- **Switch statements scattered**: Consolidated in one place
### Common Mistakes
- **Simple Factory confusion**: Factory Method uses inheritance; Simple Factory uses composition
- **Too many parameters**: Should create objects with default configuration
- **Forgetting to make factory method abstract**: Defeats the purpose of subclass specialization
### Evaluation Criteria
- **Testability**: 8/10 (easy to mock products)
- **Flexibility**: 9/10 (new products don't modify existing code)
- **Complexity**: 6/10 (adds inheritance hierarchy)
---
## Abstract Factory
### Definition
Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
### When to Use
- [x] System should be independent of how its products are created
- [x] System should be configured with one of multiple families of products
- [x] Family of related product objects must be used together
- [x] You want to provide a library of products and reveal only interfaces
### TypeScript Signature
```typescript
// Abstract products
interface AbstractProductA {
usefulFunctionA(): string;
}
interface AbstractProductB {
usefulFunctionB(): string;
anotherFunctionB(collaborator: AbstractProductA): string;
}
// Concrete products - Family 1
class ConcreteProductA1 implements AbstractProductA {
usefulFunctionA(): string {
return 'Product A1';
}
}
class ConcreteProductB1 implements AbstractProductB {
usefulFunctionB(): string {
return 'Product B1';
}
anotherFunctionB(collaborator: AbstractProductA): string {
return `B1 collaborating with ${collaborator.usefulFunctionA()}`;
}
}
// Concrete products - Family 2
class ConcreteProductA2 implements AbstractProductA {
usefulFunctionA(): string {
return 'Product A2';
}
}
class ConcreteProductB2 implements AbstractProductB {
usefulFunctionB(): string {
return 'Product B2';
}
anotherFunctionB(collaborator: AbstractProductA): string {
return `B2 collaborating with ${collaborator.usefulFunctionA()}`;
}
}
// Abstract factory
interface AbstractFactory {
createProductA(): AbstractProductA;
createProductB(): AbstractProductB;
}
// Concrete factories
class ConcreteFactory1 implements AbstractFactory {
createProductA(): AbstractProductA {
return new ConcreteProductA1();
}
createProductB(): AbstractProductB {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 implements AbstractFactory {
createProductA(): AbstractProductA {
return new ConcreteProductA2();
}
createProductB(): AbstractProductB {
return new ConcreteProductB2();
}
}
// Client code
function clientCode(factory: AbstractFactory) {
const productA = factory.createProductA();
const productB = factory.createProductB();
console.log(productB.anotherFunctionB(productA));
}
// Usage
clientCode(new ConcreteFactory1());
clientCode(new ConcreteFactory2());
```
### Detection Markers
- Multiple `create*()` methods in factory interface
- Families of related products (e.g., Button + Checkbox for Windows/Mac)
- Factory implementations return different product families
- Interface with 2+ factory methods
### Code Smells It Fixes
- **Inconsistent product families**: Ensures compatible products are created together (Windows Button + Windows Checkbox, not mixed)
- **Scattered creation logic**: Centralizes creation of related objects
### Common Mistakes
- **Over-engineering**: Often too complex for simple scenarios; Factory Method may suffice
- **Rigid product families**: Adding new product types requires changing all factories
- **Confusion with Factory Method**: Abstract Factory creates families; Factory Method creates one product type
### Evaluation Criteria
- **Testability**: 8/10 (factories are easily mocked)
- **Consistency**: 10/10 (guarantees compatible products)
- **Complexity**: 4/10 (high complexity, many classes)
---
## Builder
### Definition
Separates the construction of a complex object from its representation, allowing step-by-step construction.
### When to Use
- [x] Object has many optional parameters (>4)
- [x] Construction process should allow different representations
- [x] Need to construct complex objects step-by-step
- [x] Want to avoid "telescoping constructor" anti-pattern
### TypeScript Signature
```typescript
// Product
class House {
public walls: string = '';
public doors: number = 0;
public windows: number = 0;
public roof: string = '';
public garage: boolean = false;
public pool: boolean = false;
public describe(): string {
return `House with ${this.walls} walls, ${this.doors} doors, ${this.windows} windows, ${this.roof} roof, garage: ${this.garage}, pool: ${this.pool}`;
}
}
// Builder
class HouseBuilder {
private house: House;
constructor() {
this.house = new House();
}
public setWalls(walls: string): this {
this.house.walls = walls;
return this;
}
public setDoors(doors: number): this {
this.house.doors = doors;
return this;
}
public setWindows(windows: number): this {
this.house.windows = windows;
return this;
}
public setRoof(roof: string): this {
this.house.roof = roof;
return this;
}
public addGarage(): this {
this.house.garage = true;
return this;
}
public addPool(): this {
this.house.pool = true;
return this;
}
public build(): House {
const result = this.house;
this.house = new House(); // Reset for next build
return result;
}
}
// Usage
const house = new HouseBuilder()
.setWalls('brick')
.setDoors(2)
.setWindows(6)
.setRoof('tile')
.addGarage()
.build();
```
### Modern TypeScript Alternative (Type-Safe Builder)
```typescript
// Progressive type safety: each step unlocks the next
type HouseBuilderState<
TWalls extends boolean = false,
TRoof extends boolean = false
> = {
setWalls: TWalls extends true ? never : (walls: string) => HouseBuilderState<true, TRoof>;
setRoof: TRoof extends true ? never : (roof: string) => HouseBuilderState<TWalls, true>;
build: TWalls extends true ? (TRoof extends true ? () => House : never) : never;
};
```
### Detection Markers
- Method chaining (returns `this` or builder type)
- `build()` method returning final product
- `with*()` or `set*()` methods
- Optional fields being set incrementally
### Code Smells It Fixes
- **Telescoping constructor**: Constructor with many parameters
```typescript
// Bad
new House(walls, doors, windows, roof, garage, pool, garden, basement, ...);
// Good with Builder
new HouseBuilder().setWalls('brick').setRoof('tile').build();
```
- **Unclear parameter order**: Named methods make intent clear
- **Optional parameters complexity**: Builder handles optional features elegantly
### Common Mistakes
- **Mutable builder**: Reusing builder can lead to unexpected state
- *Solution*: Reset internal state after `build()`
- **Incomplete builder**: Not validating required fields in `build()`
- *Solution*: Use TypeScript types to enforce required steps
- **Too simple for the pattern**: If <4 parameters, constructor or object literal may be simpler
### Evaluation Criteria
- **Testability**: 9/10 (easy to create test fixtures)
- **Readability**: 10/10 (fluent interface is self-documenting)
- **Complexity**: 7/10 (adds builder class)
---
## Prototype
### Definition
Creates new objects by copying an existing object (prototype) rather than creating from scratch.
### When to Use
- [x] Object creation is expensive (complex initialization, database queries)
- [x] Need to avoid subclassing just to change initialization
- [x] System should be independent of how products are created
- [x] Classes to instantiate are specified at runtime
### TypeScript Signature
```typescript
// Prototype interface
interface Prototype {
clone(): Prototype;
}
// Concrete prototype
class ConcretePrototype implements Prototype {
public field: number;
public complexObject: { data: string };
constructor(field: number, complexObject: { data: string }) {
this.field = field;
this.complexObject = complexObject;
}
// Shallow clone
public clone(): ConcretePrototype {
return Object.create(this);
}
// Deep clone
public deepClone(): ConcretePrototype {
return new ConcretePrototype(
this.field,
{ data: this.complexObject.data } // Clone nested objects
);
}
}
// Usage
const original = new ConcretePrototype(42, { data: 'important' });
const shallowCopy = original.clone();
const deepCopy = original.deepClone();
// Shallow copy shares nested objects
shallowCopy.complexObject.data = 'modified';
console.log(original.complexObject.data); // 'modified' (!)
// Deep copy is independent
deepCopy.field = 99;
console.log(original.field); // 42 (unchanged)
```
### Modern JavaScript Alternatives
```typescript
// Spread operator (shallow)
const copy1 = { ...original };
// Object.assign (shallow)
const copy2 = Object.assign({}, original);
// structuredClone (deep, modern browsers/Node 17+)
const copy3 = structuredClone(original);
// JSON (deep, but limited: no functions, undefined, etc.)
const copy4 = JSON.parse(JSON.stringify(original));
```
### Detection Markers
- `clone()` method
- `Object.create()`
- `structuredClone()`
- `JSON.parse(JSON.stringify())` pattern
- Spread operator `{ ...obj }`
### Code Smells It Fixes
- **Expensive initialization**: Clone instead of re-initializing
- **Complex object graphs**: Cloning preserves relationships
- **Runtime type specification**: Clone prototype instead of hardcoding types
### Common Mistakes
- **Shallow vs Deep clone confusion**: Shallow clone shares nested objects
```typescript
// Dangerous if nested objects are modified
const shallow = { ...original };
```
- **Circular references**: `JSON.stringify` fails on circular references
- *Solution*: Use `structuredClone()` or custom clone logic
- **Cloning methods/functions**: Some approaches lose methods
```typescript
JSON.parse(JSON.stringify(obj)); // Loses all methods!
```
- **Not cloning private state**: Ensure all necessary state is copied
### Evaluation Criteria
- **Performance**: 9/10 (faster than re-initialization)
- **Simplicity**: 7/10 (shallow vs deep cloning is tricky)
- **Reliability**: 6/10 (easy to get wrong with nested objects)
---
## Summary Table
| Pattern | Complexity | Use Frequency | Main Benefit |
|---------|------------|---------------|--------------|
| Singleton | Low | High | Global access control |
| Factory Method | Medium | High | Decouples creation from usage |
| Abstract Factory | High | Medium | Consistent product families |
| Builder | Medium | High | Fluent construction of complex objects |
| Prototype | Low | Low | Efficient cloning |
## Best Practices
1. **Prefer composition over inheritance**: Factory and Builder often better than Singleton
2. **Use stack-native alternatives**: React Context > Singleton, DI > getInstance()
3. **TypeScript leverage**: Use generics and type constraints for type-safe builders
4. **Test-friendly design**: Avoid Singleton; use dependency injection
5. **Simplicity first**: Don't use Abstract Factory when Factory Method suffices
## References
- *Design Patterns: Elements of Reusable Object-Oriented Software* (Gang of Four)
- *Effective TypeScript* by Dan Vanderkam
- [Refactoring Guru: Creational Patterns](https://refactoring.guru/design-patterns/creational-patterns)

View file

@ -0,0 +1,308 @@
version: "1.0.0"
total_patterns: 23
description: Machine-readable index of Gang of Four design patterns for TypeScript/JavaScript
categories:
creational:
- singleton
- factory-method
- abstract-factory
- builder
- prototype
structural:
- adapter
- bridge
- composite
- decorator
- facade
- flyweight
- proxy
behavioral:
- chain-of-responsibility
- command
- iterator
- mediator
- memento
- observer
- state
- strategy
- template-method
- visitor
- interpreter
pattern_metadata:
# Creational Patterns
singleton:
difficulty: 1
use_frequency: high
detection_confidence: 0.85
typical_files: ["*Manager.ts", "*Service.ts", "*Client.ts", "*Config.ts"]
primary_signals: ["private constructor", "static getInstance", "private static instance"]
anti_patterns: ["hard to test", "global state", "tight coupling"]
related: [dependency-injection]
stack_native:
react: "Context API"
angular: "Injectable services"
nestjs: "@Injectable() decorator"
factory-method:
difficulty: 2
use_frequency: high
detection_confidence: 0.75
typical_files: ["*Factory.ts", "*Creator.ts", "*Provider.ts"]
primary_signals: ["create* method", "returns interface/abstract class", "subclass decides instantiation"]
anti_patterns: ["simple factory confusion", "too many parameters"]
related: [abstract-factory, builder]
abstract-factory:
difficulty: 3
use_frequency: medium
detection_confidence: 0.70
typical_files: ["*Factory.ts", "*AbstractFactory.ts"]
primary_signals: ["multiple create* methods", "family of related objects", "interface for factory"]
anti_patterns: ["complexity overhead", "rigid product families"]
related: [factory-method, prototype]
builder:
difficulty: 2
use_frequency: high
detection_confidence: 0.80
typical_files: ["*Builder.ts"]
primary_signals: ["fluent interface", "method chaining", "build() method", "step-by-step construction"]
anti_patterns: ["incomplete builders", "mutable builders"]
related: [factory-method]
common_use_cases: ["query builders", "request builders", "complex configuration"]
prototype:
difficulty: 2
use_frequency: low
detection_confidence: 0.65
typical_files: ["*Prototype.ts"]
primary_signals: ["clone() method", "deep copy", "Object.create()"]
anti_patterns: ["shallow vs deep copy confusion", "circular references"]
related: [factory-method]
# Structural Patterns
adapter:
difficulty: 2
use_frequency: high
detection_confidence: 0.75
typical_files: ["*Adapter.ts", "*Wrapper.ts"]
primary_signals: ["wraps incompatible interface", "delegates to adaptee", "converts interface"]
anti_patterns: ["two-way adapters", "adapter chains"]
related: [bridge, decorator, proxy]
common_use_cases: ["third-party library integration", "legacy code integration"]
bridge:
difficulty: 3
use_frequency: low
detection_confidence: 0.60
typical_files: ["*Bridge.ts", "*Abstraction.ts", "*Implementation.ts"]
primary_signals: ["abstraction has implementation reference", "two hierarchies", "runtime binding"]
anti_patterns: ["over-engineering simple scenarios"]
related: [adapter, strategy]
composite:
difficulty: 2
use_frequency: medium
detection_confidence: 0.75
typical_files: ["*Component.ts", "*Composite.ts", "*Leaf.ts"]
primary_signals: ["tree structure", "uniform interface", "recursive composition", "children collection"]
anti_patterns: ["incorrect child management", "violating uniformity"]
related: [decorator, iterator]
common_use_cases: ["UI component trees", "file systems", "organization structures"]
decorator:
difficulty: 2
use_frequency: high
detection_confidence: 0.70
typical_files: ["*Decorator.ts", "*Wrapper.ts"]
primary_signals: ["wraps same interface", "adds behavior", "delegates to wrapped", "stackable"]
anti_patterns: ["decorator explosion", "order dependency"]
related: [adapter, composite, proxy]
stack_native:
react: "Higher-Order Components (HOC)"
angular: "Directives"
nestjs: "Interceptors"
typescript: "Decorators (@)"
facade:
difficulty: 1
use_frequency: high
detection_confidence: 0.80
typical_files: ["*Facade.ts", "*API.ts", "*Interface.ts"]
primary_signals: ["simple interface to complex subsystem", "delegates to multiple classes"]
anti_patterns: ["god facade", "leaky abstraction"]
related: [adapter, mediator]
common_use_cases: ["API clients", "library wrappers", "subsystem interfaces"]
flyweight:
difficulty: 3
use_frequency: low
detection_confidence: 0.65
typical_files: ["*Flyweight.ts", "*Factory.ts"]
primary_signals: ["shared state", "intrinsic vs extrinsic state", "large number of objects", "factory for sharing"]
anti_patterns: ["premature optimization", "incorrect state separation"]
related: [singleton, factory-method]
proxy:
difficulty: 2
use_frequency: medium
detection_confidence: 0.75
typical_files: ["*Proxy.ts"]
primary_signals: ["same interface as real subject", "controls access", "lazy initialization", "logging/caching"]
anti_patterns: ["proxy chains", "performance overhead"]
related: [adapter, decorator]
common_use_cases: ["lazy loading", "access control", "caching", "logging"]
stack_native:
javascript: "Proxy object"
# Behavioral Patterns
chain-of-responsibility:
difficulty: 2
use_frequency: medium
detection_confidence: 0.70
typical_files: ["*Handler.ts", "*Middleware.ts"]
primary_signals: ["handler interface", "next handler reference", "handles or passes", "linked list structure"]
anti_patterns: ["unhandled requests", "broken chains"]
related: [command, decorator]
stack_native:
express: "Middleware chain (app.use)"
nestjs: "Guards, Interceptors, Pipes"
command:
difficulty: 2
use_frequency: medium
detection_confidence: 0.75
typical_files: ["*Command.ts", "*Action.ts"]
primary_signals: ["execute() method", "encapsulates request", "receiver reference", "undo/redo support"]
anti_patterns: ["command explosion", "complex commands"]
related: [memento, chain-of-responsibility]
stack_native:
redux: "Action creators"
vuex: "Mutations"
iterator:
difficulty: 2
use_frequency: medium
detection_confidence: 0.80
typical_files: ["*Iterator.ts"]
primary_signals: ["next() method", "hasNext()/done", "current element access", "sequential traversal"]
anti_patterns: ["modification during iteration"]
related: [composite, visitor]
stack_native:
javascript: "Iterators and generators (Symbol.iterator)"
typescript: "Iterable<T> interface"
mediator:
difficulty: 3
use_frequency: medium
detection_confidence: 0.65
typical_files: ["*Mediator.ts", "*Controller.ts"]
primary_signals: ["centralized communication", "colleagues reference mediator", "loose coupling"]
anti_patterns: ["god mediator", "tight coupling to mediator"]
related: [facade, observer]
stack_native:
react: "Context API"
angular: "Services with dependency injection"
memento:
difficulty: 3
use_frequency: low
detection_confidence: 0.60
typical_files: ["*Memento.ts", "*Snapshot.ts", "*Caretaker.ts"]
primary_signals: ["save state", "restore state", "encapsulated snapshot", "caretaker manages history"]
anti_patterns: ["large state snapshots", "exposing internals"]
related: [command]
common_use_cases: ["undo/redo", "transaction rollback", "state history"]
observer:
difficulty: 2
use_frequency: very-high
detection_confidence: 0.85
typical_files: ["*Observer.ts", "*Subject.ts", "*Publisher.ts", "*Subscriber.ts"]
primary_signals: ["subscribe/unsubscribe", "notify/emit", "observers list", "one-to-many dependency"]
anti_patterns: ["memory leaks", "notification storms", "observer order dependency"]
related: [mediator, singleton]
stack_native:
javascript: "EventEmitter, EventTarget"
react: "useState, useEffect, Context"
angular: "RxJS Observables"
rxjs: "Subject, BehaviorSubject, ReplaySubject"
vue: "ref(), reactive(), watch()"
state:
difficulty: 2
use_frequency: medium
detection_confidence: 0.70
typical_files: ["*State.ts", "*Context.ts"]
primary_signals: ["state interface", "context holds state", "state transitions", "behavior varies by state"]
anti_patterns: ["state explosion", "complex transitions"]
related: [strategy, flyweight]
stack_native:
react: "useState, useReducer"
redux: "Reducers"
xstate: "State machines"
strategy:
difficulty: 1
use_frequency: high
detection_confidence: 0.80
typical_files: ["*Strategy.ts", "*Policy.ts"]
primary_signals: ["strategy interface", "context has strategy reference", "interchangeable algorithms"]
anti_patterns: ["strategy explosion", "client awareness of strategies"]
related: [state, bridge, template-method]
stack_native:
react: "Custom hooks"
vue: "Composables"
template-method:
difficulty: 2
use_frequency: medium
detection_confidence: 0.75
typical_files: ["*Template.ts", "*Abstract*.ts"]
primary_signals: ["abstract class", "template method", "primitive operations", "hook methods"]
anti_patterns: ["too many hooks", "subclass override complexity"]
related: [strategy, factory-method]
visitor:
difficulty: 4
use_frequency: low
detection_confidence: 0.60
typical_files: ["*Visitor.ts"]
primary_signals: ["visit* methods", "double dispatch", "element accepts visitor", "operations on object structure"]
anti_patterns: ["adding new element types", "circular dependencies"]
related: [composite, iterator]
interpreter:
difficulty: 4
use_frequency: very-low
detection_confidence: 0.55
typical_files: ["*Interpreter.ts", "*Expression.ts"]
primary_signals: ["grammar rules", "interpret() method", "abstract syntax tree", "terminal/non-terminal expressions"]
anti_patterns: ["complex grammars", "performance issues"]
related: [composite, visitor]
common_use_cases: ["DSLs", "query languages", "expression evaluators"]
# Detection difficulty levels
difficulty_levels:
1: "Easy - Clear structural markers and naming conventions"
2: "Medium - Requires pattern knowledge to distinguish from similar patterns"
3: "Hard - Subtle structural differences, context-dependent"
4: "Very Hard - Requires deep analysis of intent and relationships"
# Usage frequency in modern codebases
frequency_levels:
very-high: "Ubiquitous in modern frameworks"
high: "Common in most projects"
medium: "Occasional use, specific scenarios"
low: "Rare, specialized use cases"
very-low: "Academic or niche applications"
# Confidence score interpretation
confidence_interpretation:
0.85-1.0: "High confidence - Multiple strong signals, unlikely false positive"
0.70-0.84: "Medium-high - Clear signals, some ambiguity possible"
0.55-0.69: "Medium - Requires manual validation"
0.40-0.54: "Low - Weak signals, high false positive risk"
0.0-0.39: "Very low - Naming convention only, not reliable"

View file

@ -0,0 +1,873 @@
# Structural Design Patterns
Patterns that deal with object composition and relationships between entities, providing ways to assemble objects and classes into larger structures.
## Adapter
### Definition
Converts the interface of a class into another interface clients expect, allowing incompatible interfaces to work together.
### When to Use
- [x] Want to use an existing class with an incompatible interface
- [x] Need to integrate third-party libraries with different interfaces
- [x] Want to create a reusable class that cooperates with unrelated classes
- [x] Legacy code must work with new systems
### TypeScript Signature
```typescript
// Target interface (what client expects)
interface Target {
request(): string;
}
// Adaptee (existing incompatible class)
class Adaptee {
specificRequest(): string {
return '.eetpadA eht fo roivaheb laicepS';
}
}
// Adapter (makes Adaptee compatible with Target)
class Adapter implements Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}
public request(): string {
const result = this.adaptee.specificRequest().split('').reverse().join('');
return `Adapter: ${result}`;
}
}
// Client code
function clientCode(target: Target) {
console.log(target.request());
}
// Usage
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
clientCode(adapter);
```
### Real-World Example: Third-Party Library Integration
```typescript
// Third-party library (can't modify)
class XMLDataProvider {
getXMLData(): string {
return '<data><item>1</item></data>';
}
}
// Your application expects JSON
interface JSONDataProvider {
getJSONData(): object;
}
// Adapter
class XMLToJSONAdapter implements JSONDataProvider {
constructor(private xmlProvider: XMLDataProvider) {}
getJSONData(): object {
const xml = this.xmlProvider.getXMLData();
// Convert XML to JSON (simplified)
return { data: { item: '1' } };
}
}
// Usage
const xmlProvider = new XMLDataProvider();
const adapter = new XMLToJSONAdapter(xmlProvider);
const data = adapter.getJSONData();
```
### Detection Markers
- Class implements target interface
- Holds reference to adaptee
- Delegates to adaptee with interface conversion
- Names like `*Adapter`, `*Wrapper`
### Code Smells It Fixes
- **Incompatible interfaces**: Makes legacy or third-party code compatible
- **Interface proliferation**: Single adapter vs modifying multiple client calls
### Common Mistakes
- **Two-way adapters**: Bidirectional conversion is complex; create two adapters
- **Adapter chains**: Multiple adapters in sequence indicate design issues
- **Overusing for new code**: Design compatible interfaces from the start
---
## Bridge
### Definition
Decouples an abstraction from its implementation so the two can vary independently.
### When to Use
- [x] Want to avoid permanent binding between abstraction and implementation
- [x] Both abstractions and implementations should be extensible by subclassing
- [x] Changes in implementation shouldn't affect clients
- [x] Want to share implementation among multiple objects (Flyweight-like)
### TypeScript Signature
```typescript
// Implementation interface
interface Implementation {
operationImpl(): string;
}
// Concrete implementations
class ConcreteImplementationA implements Implementation {
operationImpl(): string {
return 'ConcreteImplementationA';
}
}
class ConcreteImplementationB implements Implementation {
operationImpl(): string {
return 'ConcreteImplementationB';
}
}
// Abstraction
class Abstraction {
constructor(protected implementation: Implementation) {}
public operation(): string {
return `Abstraction: ${this.implementation.operationImpl()}`;
}
}
// Refined abstraction
class ExtendedAbstraction extends Abstraction {
public operation(): string {
return `ExtendedAbstraction: ${this.implementation.operationImpl()}`;
}
}
// Usage
const implA = new ConcreteImplementationA();
const abstraction1 = new Abstraction(implA);
console.log(abstraction1.operation());
const implB = new ConcreteImplementationB();
const abstraction2 = new ExtendedAbstraction(implB);
console.log(abstraction2.operation());
```
### Real-World Example: UI Components with Multiple Renderers
```typescript
// Implementation: Renderers
interface Renderer {
renderCircle(radius: number): string;
renderSquare(side: number): string;
}
class VectorRenderer implements Renderer {
renderCircle(radius: number): string {
return `Drawing circle (vector) with radius ${radius}`;
}
renderSquare(side: number): string {
return `Drawing square (vector) with side ${side}`;
}
}
class RasterRenderer implements Renderer {
renderCircle(radius: number): string {
return `Drawing circle (pixels) with radius ${radius}`;
}
renderSquare(side: number): string {
return `Drawing square (pixels) with side ${side}`;
}
}
// Abstraction: Shapes
abstract class Shape {
constructor(protected renderer: Renderer) {}
abstract draw(): string;
}
class Circle extends Shape {
constructor(renderer: Renderer, private radius: number) {
super(renderer);
}
draw(): string {
return this.renderer.renderCircle(this.radius);
}
}
class Square extends Shape {
constructor(renderer: Renderer, private side: number) {
super(renderer);
}
draw(): string {
return this.renderer.renderSquare(this.side);
}
}
// Usage: Can mix any shape with any renderer
const vectorCircle = new Circle(new VectorRenderer(), 5);
const rasterSquare = new Square(new RasterRenderer(), 10);
```
### Detection Markers
- Abstraction holds reference to implementation interface
- Constructor injects implementation
- Two parallel hierarchies (abstraction and implementation)
### Common Mistakes
- **Confusion with Adapter**: Bridge is design-time; Adapter is runtime fix
- **Over-engineering simple scenarios**: Use only when both hierarchies need to vary
---
## Composite
### Definition
Composes objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions uniformly.
### When to Use
- [x] Want to represent part-whole hierarchies of objects
- [x] Want clients to ignore difference between compositions and individual objects
- [x] Tree structures are natural for the domain (file systems, UI components, org charts)
### TypeScript Signature
```typescript
// Component interface
interface Component {
operation(): string;
add?(component: Component): void;
remove?(component: Component): void;
getChild?(index: number): Component;
}
// Leaf (no children)
class Leaf implements Component {
constructor(private name: string) {}
operation(): string {
return this.name;
}
}
// Composite (has children)
class Composite implements Component {
private children: Component[] = [];
constructor(private name: string) {}
add(component: Component): void {
this.children.push(component);
}
remove(component: Component): void {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
}
}
getChild(index: number): Component {
return this.children[index];
}
operation(): string {
const results = this.children.map(child => child.operation());
return `${this.name}(${results.join(', ')})`;
}
}
// Usage
const tree = new Composite('root');
const branch1 = new Composite('branch1');
branch1.add(new Leaf('leaf1'));
branch1.add(new Leaf('leaf2'));
const branch2 = new Composite('branch2');
branch2.add(new Leaf('leaf3'));
tree.add(branch1);
tree.add(branch2);
tree.add(new Leaf('leaf4'));
console.log(tree.operation());
// Output: root(branch1(leaf1, leaf2), branch2(leaf3), leaf4)
```
### Real-World Example: File System
```typescript
interface FileSystemComponent {
getName(): string;
getSize(): number;
print(indent: string): void;
}
class File implements FileSystemComponent {
constructor(private name: string, private size: number) {}
getName(): string {
return this.name;
}
getSize(): number {
return this.size;
}
print(indent: string): void {
console.log(`${indent}📄 ${this.name} (${this.size} bytes)`);
}
}
class Directory implements FileSystemComponent {
private children: FileSystemComponent[] = [];
constructor(private name: string) {}
add(component: FileSystemComponent): void {
this.children.push(component);
}
getName(): string {
return this.name;
}
getSize(): number {
return this.children.reduce((sum, child) => sum + child.getSize(), 0);
}
print(indent: string): void {
console.log(`${indent}📁 ${this.name} (${this.getSize()} bytes)`);
this.children.forEach(child => child.print(indent + ' '));
}
}
// Usage
const root = new Directory('root');
const home = new Directory('home');
home.add(new File('photo.jpg', 2048));
home.add(new File('document.pdf', 4096));
const work = new Directory('work');
work.add(new File('report.docx', 8192));
root.add(home);
root.add(work);
root.print('');
```
### Detection Markers
- Tree structure with uniform interface
- Collection of children components
- `add()`, `remove()`, `getChild()` methods
- Recursive operation calls
### Code Smells It Fixes
- **Type checking for composition vs leaf**: Uniform interface eliminates `instanceof` checks
- **Different handling for parts vs wholes**: Clients treat both uniformly
### Common Mistakes
- **Violating uniformity**: Leaf and Composite should have same interface
- **Incorrect child management**: Not handling removal properly
- **Deep recursion**: Can cause stack overflow on very deep trees
---
## Decorator
### Definition
Attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality.
### When to Use
- [x] Need to add responsibilities to objects dynamically and transparently
- [x] Responsibilities can be withdrawn
- [x] Extension by subclassing is impractical (many possible combinations)
- [x] Want to add features incrementally
### TypeScript Signature
```typescript
// Component interface
interface Component {
operation(): string;
}
// Concrete component
class ConcreteComponent implements Component {
operation(): string {
return 'ConcreteComponent';
}
}
// Base decorator
abstract class Decorator implements Component {
constructor(protected component: Component) {}
operation(): string {
return this.component.operation();
}
}
// Concrete decorators
class DecoratorA extends Decorator {
operation(): string {
return `DecoratorA(${super.operation()})`;
}
}
class DecoratorB extends Decorator {
operation(): string {
return `DecoratorB(${super.operation()})`;
}
}
// Usage: Stack decorators
const simple = new ConcreteComponent();
const decorated1 = new DecoratorA(simple);
const decorated2 = new DecoratorB(decorated1);
console.log(decorated2.operation());
// Output: DecoratorB(DecoratorA(ConcreteComponent))
```
### Stack-Native Alternatives
**React - Higher-Order Components**:
```typescript
// HOC decorator
function withAuth<P extends object>(
Component: React.ComponentType<P>
): React.ComponentType<P> {
return (props: P) => {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <Component {...props} />;
};
}
// Usage: Stack decorators
const AuthenticatedProfile = withAuth(Profile);
const AuthenticatedAdminProfile = withLogging(withAuth(Profile));
```
**NestJS - Interceptors**:
```typescript
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
return next.handle().pipe(
tap(() => console.log('After...'))
);
}
}
// Apply decorator
@UseInterceptors(LoggingInterceptor)
@Controller('users')
export class UsersController {}
```
### Detection Markers
- Implements same interface as wrapped object
- Holds reference to wrapped object
- Delegates to wrapped, adding behavior
- Can be stacked
### Code Smells It Fixes
- **Class explosion**: Avoid creating subclass for every feature combination
- **Rigid feature addition**: Add/remove features dynamically
### Common Mistakes
- **Order dependency**: DecoratorA(DecoratorB(x)) ≠ DecoratorB(DecoratorA(x))
- **Decorator explosion**: Too many small decorators can be hard to manage
- **Breaking interface**: Decorator must maintain interface contract
---
## Facade
### Definition
Provides a unified interface to a set of interfaces in a subsystem, making the subsystem easier to use.
### When to Use
- [x] Want to provide a simple interface to a complex subsystem
- [x] Many dependencies exist between clients and implementation classes
- [x] Want to layer subsystems
- [x] Need to decouple subsystem from clients
### TypeScript Signature
```typescript
// Complex subsystem classes
class SubsystemA {
operationA(): string {
return 'SubsystemA';
}
}
class SubsystemB {
operationB(): string {
return 'SubsystemB';
}
}
class SubsystemC {
operationC(): string {
return 'SubsystemC';
}
}
// Facade
class Facade {
private subsystemA: SubsystemA;
private subsystemB: SubsystemB;
private subsystemC: SubsystemC;
constructor() {
this.subsystemA = new SubsystemA();
this.subsystemB = new SubsystemB();
this.subsystemC = new SubsystemC();
}
// Simplified interface
public simpleOperation(): string {
const resultA = this.subsystemA.operationA();
const resultB = this.subsystemB.operationB();
const resultC = this.subsystemC.operationC();
return `Facade coordinates: ${resultA}, ${resultB}, ${resultC}`;
}
}
// Client code
const facade = new Facade();
console.log(facade.simpleOperation());
// Instead of:
// const a = new SubsystemA(); const b = new SubsystemB(); const c = new SubsystemC();
// a.operationA(); b.operationB(); c.operationC();
```
### Real-World Example: Payment Processing
```typescript
// Complex subsystems
class PaymentValidator {
validate(amount: number, card: string): boolean {
// Complex validation logic
return amount > 0 && card.length === 16;
}
}
class PaymentGateway {
charge(amount: number, card: string): string {
return `Charged $${amount} to ${card}`;
}
}
class NotificationService {
sendReceipt(email: string, transactionId: string): void {
console.log(`Receipt sent to ${email}: ${transactionId}`);
}
}
class TransactionLogger {
log(transaction: string): void {
console.log(`Logged: ${transaction}`);
}
}
// Facade
class PaymentFacade {
private validator = new PaymentValidator();
private gateway = new PaymentGateway();
private notifications = new NotificationService();
private logger = new TransactionLogger();
processPayment(amount: number, card: string, email: string): boolean {
// Simplified interface for complex process
if (!this.validator.validate(amount, card)) {
return false;
}
const result = this.gateway.charge(amount, card);
this.logger.log(result);
this.notifications.sendReceipt(email, result);
return true;
}
}
// Client code (simple!)
const payment = new PaymentFacade();
payment.processPayment(100, '1234567890123456', 'user@example.com');
```
### Detection Markers
- Class with multiple subsystem dependencies
- Simple public methods coordinating subsystems
- Named `*Facade`, `*API`, `*Service`
### Code Smells It Fixes
- **Complex subsystem usage**: Clients don't need to know subsystem details
- **Tight coupling**: Clients depend on facade, not many classes
### Common Mistakes
- **God Facade**: Facade does too much; should coordinate, not contain logic
- **Leaky abstraction**: Exposing subsystem details defeats the purpose
---
## Flyweight
### Definition
Uses sharing to support large numbers of fine-grained objects efficiently by storing shared state externally.
### When to Use
- [x] Application uses large number of objects
- [x] Storage cost is high due to object quantity
- [x] Most object state can be made extrinsic (externalized)
- [x] Many groups of objects may be replaced by relatively few shared objects
### TypeScript Signature
```typescript
// Flyweight
class Flyweight {
constructor(private sharedState: string) {}
operation(uniqueState: string): void {
console.log(`Flyweight: Shared (${this.sharedState}) and unique (${uniqueState}) state.`);
}
}
// Flyweight factory
class FlyweightFactory {
private flyweights: Map<string, Flyweight> = new Map();
constructor(initialFlyweights: string[][]) {
for (const state of initialFlyweights) {
this.flyweights.set(this.getKey(state), new Flyweight(state.join('_')));
}
}
private getKey(state: string[]): string {
return state.join('_');
}
getFlyweight(sharedState: string[]): Flyweight {
const key = this.getKey(sharedState);
if (!this.flyweights.has(key)) {
console.log('Creating new flyweight');
this.flyweights.set(key, new Flyweight(key));
} else {
console.log('Reusing existing flyweight');
}
return this.flyweights.get(key)!;
}
listFlyweights(): void {
console.log(`FlyweightFactory: ${this.flyweights.size} flyweights:`);
for (const key of this.flyweights.keys()) {
console.log(key);
}
}
}
// Usage
const factory = new FlyweightFactory([
['Chevrolet', 'Camaro2018', 'pink'],
['Mercedes Benz', 'C300', 'black'],
]);
const flyweight1 = factory.getFlyweight(['Chevrolet', 'Camaro2018', 'pink']);
flyweight1.operation('license-123');
const flyweight2 = factory.getFlyweight(['Chevrolet', 'Camaro2018', 'pink']);
flyweight2.operation('license-456'); // Reuses same flyweight
```
### Real-World Example: Text Editor Characters
```typescript
// Flyweight: Character formatting (shared)
class CharacterFormat {
constructor(
public font: string,
public size: number,
public color: string
) {}
}
// Flyweight factory
class FormatFactory {
private formats = new Map<string, CharacterFormat>();
getFormat(font: string, size: number, color: string): CharacterFormat {
const key = `${font}_${size}_${color}`;
if (!this.formats.has(key)) {
this.formats.set(key, new CharacterFormat(font, size, color));
}
return this.formats.get(key)!;
}
}
// Character with extrinsic state
class Character {
constructor(
private char: string,
private format: CharacterFormat // Shared flyweight
) {}
render(position: number): string {
return `'${this.char}' at ${position} (${this.format.font}, ${this.format.size}px, ${this.format.color})`;
}
}
// Document
const formatFactory = new FormatFactory();
const arial12Black = formatFactory.getFormat('Arial', 12, 'black');
const arial12Red = formatFactory.getFormat('Arial', 12, 'red');
// 10,000 characters, but only 2 format objects
const characters: Character[] = [];
for (let i = 0; i < 10000; i++) {
const format = i % 2 === 0 ? arial12Black : arial12Red;
characters.push(new Character('A', format));
}
```
### Detection Markers
- Factory managing pool of shared objects
- Intrinsic (shared) vs extrinsic (unique) state separation
- Map/cache of flyweights
### Common Mistakes
- **Premature optimization**: Only use if memory is actually a problem
- **Incorrect state separation**: Mixing intrinsic and extrinsic state
---
## Proxy
### Definition
Provides a surrogate or placeholder for another object to control access to it.
### When to Use
- [x] Lazy initialization (virtual proxy): Create expensive object only when needed
- [x] Access control (protection proxy): Control access to original object
- [x] Local representative of remote object (remote proxy)
- [x] Logging, caching, or monitoring access
### TypeScript Signature
```typescript
// Subject interface
interface Subject {
request(): void;
}
// Real subject
class RealSubject implements Subject {
request(): void {
console.log('RealSubject: Handling request');
}
}
// Proxy
class Proxy implements Subject {
private realSubject: RealSubject | null = null;
request(): void {
// Access control
if (this.checkAccess()) {
// Lazy initialization
if (!this.realSubject) {
this.realSubject = new RealSubject();
}
// Logging
this.logAccess();
// Delegate to real subject
this.realSubject.request();
}
}
private checkAccess(): boolean {
console.log('Proxy: Checking access');
return true;
}
private logAccess(): void {
console.log('Proxy: Logging access time');
}
}
// Usage
const proxy = new Proxy();
proxy.request();
// Output:
// Proxy: Checking access
// Proxy: Logging access time
// RealSubject: Handling request
```
### Modern JavaScript Proxy
```typescript
const target = {
message: 'Hello',
getValue() {
return this.message;
}
};
const handler = {
get(target: any, prop: string) {
console.log(`Accessing property: ${prop}`);
return target[prop];
},
set(target: any, prop: string, value: any) {
console.log(`Setting property: ${prop} = ${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // Logs: Accessing property: message
proxy.message = 'World'; // Logs: Setting property: message = World
```
### Detection Markers
- Implements same interface as real subject
- Holds reference to real subject
- Controls access (checks, logging, caching)
- Lazy initialization of real subject
### Common Mistakes
- **Proxy chains**: Multiple proxies wrapping each other
- **Performance overhead**: Every access goes through proxy
- **Confusion with Decorator**: Proxy controls access; Decorator adds behavior
---
## Summary Table
| Pattern | Complexity | Use Frequency | Main Benefit |
|---------|------------|---------------|--------------|
| Adapter | Low | High | Interface compatibility |
| Bridge | High | Low | Decouple abstraction from implementation |
| Composite | Medium | High | Uniform tree structure handling |
| Decorator | Medium | High | Dynamic responsibility addition |
| Facade | Low | Very High | Simplified subsystem interface |
| Flyweight | High | Low | Memory optimization |
| Proxy | Medium | Medium | Controlled access |
## Best Practices
1. **Adapter vs Bridge**: Adapter fixes incompatibility; Bridge designs flexibility
2. **Decorator vs Proxy**: Decorator adds features; Proxy controls access
3. **Facade simplicity**: Should coordinate, not contain business logic
4. **Composite uniformity**: Leaf and Composite must share interface
5. **Use native Proxy**: JavaScript `Proxy` object for dynamic property access
## References
- *Design Patterns: Elements of Reusable Object-Oriented Software* (Gang of Four)
- [MDN: Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
- [Refactoring Guru: Structural Patterns](https://refactoring.guru/design-patterns/structural-patterns)