The Decorator and Proxy design patterns are two patterns that are often confused with each other.
And honestly, the confusion is understandable.
Both patterns wrap another object. Both implement the same interface as the object they wrap. And both can execute code before or after calling the wrapped object.
So if they look almost identical in code, what actually makes them different?
The main difference is their intent.
A Decorator adds behavior to an object, while a Proxy controls access to an object.
The implementation can look very similar, but the reason you introduce the wrapper is different.
First, what problem are we trying to solve?
Imagine we have a simple service:
public interface IOrderService
{
Task<Order> GetOrderAsync(Guid id);
}
And an implementation:
public class OrderService : IOrderService
{
public async Task<Order> GetOrderAsync(Guid id)
{
// Retrieve order
return await GetFromDatabaseAsync(id);
}
}
Over time, we may want to add logging, caching, authorization, metrics, retry policies, or other behavior.
One option would be to put everything inside OrderService:
public async Task<Order> GetOrderAsync(Guid id)
{
LogRequest(id);
CheckAuthorization();
if (cache.TryGetValue(id, out Order? cachedOrder))
return cachedOrder;
var order = await GetFromDatabaseAsync(id);
cache.Set(id, order);
RecordMetric();
return order;
}
The service now knows about database access, caching, authorization, logging, and metrics.
This is where wrapping becomes useful.
The Decorator Pattern
The Decorator pattern allows us to wrap an object and add responsibilities to it without modifying the original implementation.
Let's say we want to add caching.
We can create a decorator:
public class CachedOrderService : IOrderService
{
private readonly IOrderService orderService;
private readonly IMemoryCache cache;
public CachedOrderService(
IOrderService orderService,
IMemoryCache cache)
{
orderService= orderService;
cache = cache;
}
public async Task<Order> GetOrderAsync(Guid id)
{
if (cache.TryGetValue(id, out Order? order))
return order!;
order = await orderService.GetOrderAsync(id);
cache.Set(id, order);
return order;
}
}
The caller doesn't need to know that caching exists.
It simply calls:
await orderService.GetOrderAsync(orderId);
The decorator intercepts the call, performs its additional behavior, and delegates the actual operation to the wrapped service.
For example:
public class LoggingOrderService : IOrderService
{
private readonly IOrderService orderService;
private readonly ILogger<LoggingOrderService> logger;
public LoggingOrderService(
IOrderService orderService,
ILogger<LoggingOrderService> logger)
{
orderService = orderService;
logger = logger;
}
public async Task<Order> GetOrderAsync(Guid id)
{
logger.LogInformation(
"Getting order {OrderId}",
id);
var result = await orderService.GetOrderAsync(id);
logger.LogInformation(
"Order {OrderId} retrieved",
id);
return result;
}
}
The Proxy Pattern
A Proxy also wraps another object, but the motivation is different.
A proxy represents another object and controls access to it.
For example, imagine that accessing our order service is expensive because it communicates with an external service.
We don't necessarily want to call the real service every time.
We can create a proxy:
public class OrderServiceProxy : IOrderService
{
private readonly IOrderService realService;
public OrderServiceProxy(IOrderService realService)
{
realService = realService;
}
public async Task<Order> GetOrderAsync(Guid id)
{
if (!UserHasAccessToOrder(id))
throw new UnauthorizedAccessException();
return await realService.GetOrderAsync(id);
}
private bool UserHasAccessToOrder(Guid id)
{
// Authorization logic
return true;
}
}
The proxy controls whether the real object can be accessed.
The caller still sees:
IOrderService
The proxy can decide whether the operation should happen at all.
So what's the actual difference?
The easiest way to remember the difference is to ask:
"Why am I wrapping this object?"
If the answer is:
"I want to add behavior."
You're probably looking at a Decorator.
If the answer is:
"I want to control access to the object."
You're probably looking at a Proxy.
But why do their implementations look so similar?
Because both patterns use composition.
A typical implementation looks like:
public class Wrapper : IService
{
private readonly IService service;
public Wrapper(IService service)
{
service = service;
}
public void Execute()
{
// Something
service.Execute();
// Something else
}
}
This structure can represent either a Decorator or a Proxy.
The code alone doesn't necessarily tell you which pattern you're looking at.
The intent does.
That's one of the most important things to understand about design patterns.
Design patterns aren't just specific class structures. They're reusable solutions to recurring design problems, and their intent matters.
Decorator and Proxy can sometimes overlap
This is where things become interesting.
Imagine this:
public class CachedOrderService : IOrderService
{
private readonly IOrderService orderService;
public async Task<Order> GetOrderAsync(Guid id)
{
if (cache.TryGetValue(id, out Order? order))
return order!;
return await orderService.GetOrderAsync(id);
}
}
Is this a Proxy or a Decorator?
It depends on the intent.
If caching is considered an additional responsibility applied to the service, we'd normally call it a Decorator.
If the object is primarily representing the underlying service and controlling how/when that service is accessed, it can be considered a Proxy.
The same structural technique can implement different patterns.
A useful comparison
| Decorator | Proxy |
|---|
| Adds behavior | Controls access |
| Usually preserves the original behavior | May prevent or alter access |
| Focuses on extending responsibilities | Focuses on controlling the underlying object |