Proxy vs Decorator
Proxy and Decorator have similar structures but different intents. Understanding when to use each is important.
Structural Similarity
Both wrap an object and delegate to it:
typescript1// Decorator structure2class LoggingDecorator implements ShippingService {3 constructor(private wrapped: ShippingService) {}45 async getRates(request: RateRequest): Promise<Rate[]> {6 console.log('Getting rates...');7 return this.wrapped.getRates(request);8 }9}1011// Proxy structure12class LoggingProxy implements ShippingService {13 constructor(private real: ShippingService) {}1415 async getRates(request: RateRequest): Promise<Rate[]> {16 console.log('Getting rates...');17 return this.real.getRates(request);18 }19}2021// Look identical!
Key Differences
Intent
| Aspect | Decorator | Proxy |
|---|---|---|
| Purpose | Add behavior | Control access |
| Focus | Enhancement | Management |
| Client awareness | Knows about decoration | Transparent |
Lifecycle Control
typescript1// Decorator: Client creates both2const service = new LoggingDecorator(new CarrierAPI());34// Proxy: Often controls creation of real object5class LazyProxy implements ShippingService {6 private real: CarrierAPI | null = null; // Proxy decides when to create78 async getRates(request: RateRequest): Promise<Rate[]> {9 if (!this.real) {10 this.real = new CarrierAPI(); // Created on first use11 }12 return this.real.getRates(request);13 }14}
Access Control
typescript1// Proxy: Can prevent access entirely2class AuthProxy implements ShippingService {3 constructor(private real: ShippingService) {}45 async getRates(request: RateRequest): Promise<Rate[]> {6 if (!this.isAuthorized()) {7 throw new UnauthorizedError(); // Blocks access8 }9 return this.real.getRates(request);10 }11}1213// Decorator: Adds behavior but doesn't prevent access14class AuditDecorator implements ShippingService {15 async getRates(request: RateRequest): Promise<Rate[]> {16 this.audit('getRates', request); // Adds logging17 return this.wrapped.getRates(request); // Always proceeds18 }19}
Decision Guide
Use Decorator When:
typescript1// Adding optional features2class InsuredShipment extends ShipmentDecorator {3 getCost(): number {4 return this.wrapped.getCost() + this.insurancePremium;5 }6}78// Stacking multiple enhancements9let shipment: Shipment = new BasicShipment();10shipment = new InsuredShipment(shipment);11shipment = new ExpressShipment(shipment);12shipment = new FragileShipment(shipment);1314// Key: Each decorator adds value
Use Proxy When:
typescript1// Controlling access2class CachingProxy {3 async getRates(request: RateRequest): Promise<Rate[]> {4 const cached = this.cache.get(key);5 if (cached) return cached; // May not call real at all6 return this.real.getRates(request);7 }8}910// Managing lifecycle11class PooledConnectionProxy {12 private connection: Connection | null = null;1314 async query(sql: string): Promise<any> {15 if (!this.connection) {16 this.connection = await this.pool.acquire();17 }18 return this.connection.query(sql);19 }2021 release(): void {22 if (this.connection) {23 this.pool.release(this.connection);24 this.connection = null;25 }26 }27}2829// Key: Proxy controls when/if real object is used
Combined Example
Sometimes you need both:
typescript1// Proxy for access control2class SecuredCarrierProxy implements ShippingService {3 constructor(4 private real: ShippingService,5 private auth: AuthService6 ) {}78 async getRates(request: RateRequest): Promise<Rate[]> {9 await this.auth.checkPermission('shipping:read');10 return this.real.getRates(request);11 }12}1314// Decorator for adding features15class InsuranceDecorator implements ShippingService {16 async getRates(request: RateRequest): Promise<Rate[]> {17 const rates = await this.wrapped.getRates(request);18 return rates.map(rate => ({19 ...rate,20 price: rate.price + this.calculateInsurance(rate)21 }));22 }23}2425// Combine them26const api = new CarrierAPI();27const secured = new SecuredCarrierProxy(api, authService);28const withInsurance = new InsuranceDecorator(secured);
Naming Conventions
typescript1// Decorators often describe what they add2class LoggingShipmentDecorator { }3class InsuredShipmentDecorator { }4class FragileHandlingDecorator { }56// Proxies often describe their control purpose7class CachingProxy { }8class LazyInitializationProxy { }9class AccessControlProxy { }10class RateLimitingProxy { }
Summary
| Question | Decorator | Proxy |
|---|---|---|
| Does it add features? | Yes | No (controls access) |
| Does it control creation? | No | Often yes |
| Can it prevent operation? | Rarely | Yes |
| Is stacking common? | Yes | Sometimes |
| Client aware? | Usually | Ideally not |
Think:
- Decorator: "Add more to what it does"
- Proxy: "Control how/when/if it does it"