Introduction
The previous two design pattern posts were about the Decorator Pattern and the Proxy Design Pattern. These pattern look very similar though they have different purposes. The implementation or the mechanism used look very similar and can confuse anyone. In this post, I will show you one more example of the Decorator Pattern that is very close to the example seen in the Proxy Design Pattern. Then, I’ll explain the difference between the Proxy Pattern and the Decorator Pattern.
A (very) quick summary
Decorator Pattern: The Decorator design pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Proxy Pattern: The Proxy Design Pattern provides a surrogate or placeholder for another object to control access to it.
Stock Service (again)
In the proxy design pattern example, we used a proxy to cache the responses from a service that returns the stock prices. Now, we will apply the decorator design pattern decorating the stock service. I have changed the contact or interface of the Stock service a bit for this example.
The StockDetailsService is responsible for returning several details about a stock symbol. The implementation used here returns the open, high, low values and the current price for a stock symbol.
public interface StockDetailsService {
Map<String, String> getStockDetails(String symbol);
}
public class StockDetailsServiceImpl implements StockDetailsService {
@Override
public Map<String, String> getStockDetails(String symbol) {
//Call service and get the stock details
System.out.println("Calling service to get stock details for symbol " + symbol);
//Returning a fake/hard-coded response here
Map<String, String> stockDetails = new HashMap<>();
stockDetails.put("open", "144.4000");
stockDetails.put("high", "145.6700");
stockDetails.put("low", "143.5100");
stockDetails.put("price", "144.1900");
return stockDetails;
}
}
Decorating the StockDetailsServiceImpl
Let us say we want the stock service’s response to also include the name of the stock symbol as one of the map’s entry. This is a new behaviour. Decorator pattern does just that!!
Let us create a wrapper (decorator) over the StockDetailsServiceImpl to do just that.
/**
* Adds the name of the stock to the stock details.
*/
public class DecoratedStockDetailsService implements StockDetailsService {
private final StockDetailsService stockDetailsService;
public DecoratedStockDetailsService(StockDetailsService stockDetailsService) {
this.stockDetailsService = stockDetailsService;
}
@Override
public Map<String, String> getStockDetails(String symbol) {
Map<String, String> stockDetails = this.stockDetailsService.getStockDetails(symbol);
//Add the symbol to the returned data
Map<String, String> decoratedStockDetails = new HashMap<>(stockDetails);
decoratedStockDetails.put("symbol", symbol);
return decoratedStockDetails; //return the 'decorated' data
}
}
Let us see this in action
public static void main(String[] args) {
StockDetailsService stockDetailsService = new StockDetailsServiceImpl();
StockDetailsService symbolAddingStockDetailsService = new DecoratedStockDetailsService(stockDetailsService);
System.out.println(symbolAddingStockDetailsService.getStockDetails("MSFT"));
}
Outputs:
Calling service to get stock details for symbol MSFT
{symbol=MSFT, high=145.6700, low=143.5100, price=144.1900, open=144.4000}
The caller or the client is calling the getStockDetails method on the StockDetailsService interface. But we have wrapped the StockDetailsService implementation within our Decorator that adds the name of the symbol to the underlying service’s response. Thus, the final response sent back to the client also has the stock symbol name.
Comparing the Decorator and the Proxy examples
Now since we have looked at the Decorator over the stock details service, I recommend checking out the caching proxy used in the Proxy Design pattern post.
Now, they look very similar, don’t they? The common thing is they (decorator or the proxy) wrap over something. Here, that something is the actual stock service implementation. Hence, from that standpoint, they look very much the same. But they are different in two aspects
- How the thing they wrap is created (the delegate’s lifecycle)
- their responsibilities
Difference #1 - The delegate’s lifecycle
It is very clear that both the decorator and the proxy depends on or is associated with the delegate. This generically means associativity. But decorator uses aggregation whereas a proxy uses composition.
Aggregation vs Composition
Association:
This means that two classes are dependent on each other when one has a reference to the other. When the dependency is on either side, it becomes a bi-directional association.
Example: An order has a list of line items (uni-directional association from the Order to the Line Items). When the individual line items has the reference back to the order, it becomes a bi-directional one.
Based on the type of relationship between the two classes (the parent and the child), the association can either be aggregation or composition.
Aggregation:
When the child class can exist independently of the parent class.
Example: Consider a Car and a Wheel. When there is no car object, the wheels can still exist (say they can still be part of a bus or a truck).
Composition:
When the child class cannot exist independently of the parent class.
Example: A Library class has a set of Accounts. When the library object is deleted, the accounts cannot stand on their own i.e, they have no meaning without the parent library object.
Applying aggregation and composition to the Decorator and the Proxy
This infers that the delegate instance is always passed to the Decorator. The decorator does not know the concrete type of the delegate. In other words, the decorator does not create the delegate instance and does not know exactly what it is decorating.
In our earlier example, the DecoratedStockDetailsService gets a StockDetailsService through the constructor. It does not know it’s actual concrete type. The delegate object is created and maintained by the caller i.e., the constructor of the delegate. The delegate is not owned by the decorator and thus it is an aggregate.
On the other hand, in the proxy case, the proxy (usually) is responsible for creating the delegate object and hence it manages the delegate. The proxy thus holds a reference to a subclass of its type. Since, the delegate does not exist without the proxy, it is a composite of the proxy.
NOTE: You might have noticed that the example of the Proxy in the previous post is not as described above. The proxy was passed the delegate (in the respect it looks like a decorator). But, still it is a proxy and delegate can be passed to a proxy at times too. This was called out in the Participants section.
Thus, a proxy can come in both flavours. So, how to differentiate between a proxy and a decorator when a proxy does not manage its delegate. The next difference would be the tie-breaker.
Difference #2 - The Responsibility
A decorator is always used to add new behaviours to an object whereas a proxy is used to manage/control access to an object.
Looking back at the example at the start of the post, the decorator adds a new behaviour by adding the stock symbol to the map. The default implementation of the Stock Details Service did not provide this capability.
On the contrary all proxies control object access - like a caching proxy we have seen. They did not change or alter the behaviour of any class. A proxy is used for lazy initialization, caching and to control access based on rights.
Wrapping the Decorator in a Caching proxy
Before I wrap up this post, let me show the usage of a caching proxy over the decorator created above.
public class CachedStockDetailsProvider implements StockDetailsService {
private final StockDetailsService stockDetailsService;
private final Map<String, Map<String, String>> cache;
public CachedStockDetailsProvider() {
this.stockDetailsService = new DecoratedStockDetailsService(new StockDetailsServiceImpl());
this.cache = new ConcurrentHashMap<>();
}
@Override
public Map<String, String> getStockDetails(String symbol) {
return cache.computeIfAbsent(symbol, stockDetailsService::getStockDetails);
}
}
public static void main(String[] args) {
....
//When using proxy
StockDetailsService proxyStockDetailsService = new CachedStockDetailsProvider();
System.out.println(proxyStockDetailsService.getStockDetails("MSFT"));
System.out.println(proxyStockDetailsService.getStockDetails("MSFT"));
System.out.println(proxyStockDetailsService.getStockDetails("MSFT"));
}
Outputs:
Calling service to get stock details for symbol MSFT
{symbol=MSFT, high=145.6700, low=143.5100, price=144.1900, open=144.4000}
{symbol=MSFT, high=145.6700, low=143.5100, price=144.1900, open=144.4000}
{symbol=MSFT, high=145.6700, low=143.5100, price=144.1900, open=144.4000}
Irrespective of how many calls are made for a stock symbol, the external stock details service is invoked only one. The subsequent follow-up calls gets the result from the cache (just a map here).
Conclusion on Proxy Pattern vs Decorator Pattern
As a follow-up of the Decorator and Proxy design patterns, we looked at the key differences between them.
Summarizing the key points:
- A decorator is passed its delegate while the proxy creates its delegate (but sometimes a proxy is passed its delegate). Thus, a delegate does not know the actual subclass instance.
- Decorator uses aggregation and a proxy uses composition.
- Decorate is used to enhance or decorate an interface by providing additional behaviour. A proxy is responsible for managing access to objects.
- With a decorator, we can nest or wrap recursively (depth > 1). A proxy, has just the delegate. (But the delegate can be a decorator as seen in the previous section).