Comparing Decorator Pattern and Proxy Pattern in Software Design

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.

Tags: Decorator Pattern Proxy Pattern Design Patterns java MyBatis

Posted on Sun, 10 May 2026 08:46:01 +0000 by fandelem