The Decorator Pattern enables dynamic augmentation of functionality, offering a more flexible approach than single inheritance. It allows runtime extension of base class capabilities through composition.
Flexible Functionality Enhancement
Traditional inheritance creates rigid hierarchies, while decorators provide modular enhancements that can be added or removed at runtime.
Example: Caching Token Service
public interface TokenService<T, U, V> {
U requestAuthorization(T credentials);
V obtainToken(U authResponse);
}
public class BasicTokenService implements TokenService<Credentials, AuthResponse, TokenResponse> {
public AuthResponse requestAuthorization(Credentials creds) {
// Implementation for authorization request
return new AuthResponse();
}
public TokenResponse obtainToken(AuthResponse auth) {
// Implementation for token retrieval
return new TokenResponse();
}
}
public class CachedTokenService implements TokenService<Credentials, AuthResponse, TokenResponse> {
private TokenService baseService;
public CachedTokenService(TokenService service) {
this.baseService = service;
}
public AuthResponse requestAuthorization(Credentials creds) {
// Check cache first, then delegate if needed
return baseService.requestAuthorization(creds);
}
public TokenResponse obtainToken(AuthResponse auth) {
// Check cache first, then delegate if needed
return baseService.obtainToken(auth);
}
}
Real-world Implementation: MyBatis Executor
MyBatis uses decorators extensively, such as the CachingExecutor which wraps base executors to add caching functionality:
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager cacheManager;
public CachingExecutor(Executor executor) {
this.delegate = executor;
this.cacheManager = new TransactionalCacheManager();
}
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds bounds, ResultHandler handler) {
// Cache checking and population logic
Cache cache = ms.getCache();
if (cache != null) {
CacheKey key = createCacheKey(ms, parameter, bounds, ms.getBoundSql(parameter));
List<E> result = (List<E>) cacheManager.getObject(cache, key);
if (result == null) {
result = delegate.query(ms, parameter, bounds, handler);
cacheManager.putObject(cache, key, result);
}
return result;
}
return delegate.query(ms, parameter, bounds, handler);
}
}
Understanding the Proxy Pattern
The Proxy Pattern primarily focuses on controlling access to real objects, with functionality enhancement being a secondary benefit. It acts as an intermediary between clients and actual implementations.
Access Control Mechanism
Proxies manage how clients interact with target objects, implementing security, lazy initialization, or remote access controls. This pattern is exemplified in reverse proxy servers like Nginx, which manage and route requests to backend services.