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:
parent
936f8dd564
commit
fcc6f2dba3
12 changed files with 5842 additions and 3 deletions
1032
examples/skills/design-patterns/reference/behavioral.md
Normal file
1032
examples/skills/design-patterns/reference/behavioral.md
Normal file
File diff suppressed because it is too large
Load diff
562
examples/skills/design-patterns/reference/creational.md
Normal file
562
examples/skills/design-patterns/reference/creational.md
Normal 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)
|
||||
308
examples/skills/design-patterns/reference/patterns-index.yaml
Normal file
308
examples/skills/design-patterns/reference/patterns-index.yaml
Normal 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"
|
||||
873
examples/skills/design-patterns/reference/structural.md
Normal file
873
examples/skills/design-patterns/reference/structural.md
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue