Decorator Pattern
1. Giới Thiệu
1.1 Định Nghĩa
Decorator Pattern là một structural pattern cho phép thêm các hành vi mới vào đối tượng hiện có một cách linh hoạt bằng cách đặt các đối tượng này bên trong các đối tượng wrapper.
1.2 Mục Đích
- Thêm chức năng mới cho đối tượng mà không thay đổi cấu trúc
- Hỗ trợ mở rộng chức năng động trong runtime
- Thay thế cho kế thừa trong một số trường hợp
- Kết hợp nhiều hành vi một cách linh hoạt
2. Cấu Trúc Cơ Bản
2.1 Component Interface
// Component interface
public interface Coffee {
String getDescription();
double getCost();
}
// Concrete component
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 1.0;
}
}
// Base decorator
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
// Concrete decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
}
public class WhipDecorator extends CoffeeDecorator {
public WhipDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Whip";
}
@Override
public double getCost() {
return super.getCost() + 0.7;
}
}
3. Ví Dụ Thực Tế
3.1 Data Source Decorator
// Component interface
public interface DataSource {
void writeData(String data);
String readData();
}
// Concrete component
public class FileDataSource implements DataSource {
private String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
@Override
public void writeData(String data) {
// Write to file
System.out.println("Writing data to file: " + data);
}
@Override
public String readData() {
// Read from file
return "Data from file";
}
}
// Base decorator
public class DataSourceDecorator implements DataSource {
private DataSource wrapper;
public DataSourceDecorator(DataSource source) {
this.wrapper = source;
}
@Override
public void writeData(String data) {
wrapper.writeData(data);
}
@Override
public String readData() {
return wrapper.readData();
}
}
// Concrete decorators
public class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) {
super(source);
}
@Override
public void writeData(String data) {
String encrypted = encrypt(data);
super.writeData(encrypted);
}
@Override
public String readData() {
String data = super.readData();
return decrypt(data);
}
private String encrypt(String data) {
// Encryption logic
return "ENCRYPTED[" + data + "]";
}
private String decrypt(String data) {
// Decryption logic
return data.replace("ENCRYPTED[", "")
.replace("]", "");
}
}
public class CompressionDecorator extends DataSourceDecorator {
public CompressionDecorator(DataSource source) {
super(source);
}
@Override
public void writeData(String data) {
String compressed = compress(data);
super.writeData(compressed);
}
@Override
public String readData() {
String data = super.readData();
return decompress(data);
}
private String compress(String data) {
// Compression logic
return "COMPRESSED[" + data + "]";
}
private String decompress(String data) {
// Decompression logic
return data.replace("COMPRESSED[", "")
.replace("]", "");
}
}
3.2 Web Service Decorator
// Component interface
public interface WebService {
Response execute(Request request);
}
// Concrete component
public class BasicWebService implements WebService {
@Override
public Response execute(Request request) {
// Execute request
return new Response("Basic response");
}
}
// Base decorator
public class WebServiceDecorator implements WebService {
protected WebService service;
public WebServiceDecorator(WebService service) {
this.service = service;
}
@Override
public Response execute(Request request) {
return service.execute(request);
}
}
// Concrete decorators
public class LoggingDecorator extends WebServiceDecorator {
private Logger logger = LoggerFactory.getLogger(
LoggingDecorator.class);
public LoggingDecorator(WebService service) {
super(service);
}
@Override
public Response execute(Request request) {
logger.info("Before executing request");
Response response = super.execute(request);
logger.info("After executing request");
return response;
}
}
public class CachingDecorator extends WebServiceDecorator {
private Map<String, Response> cache = new HashMap<>();
public CachingDecorator(WebService service) {
super(service);
}
@Override
public Response execute(Request request) {
String key = request.getKey();
if (cache.containsKey(key)) {
return cache.get(key);
}
Response response = super.execute(request);
cache.put(key, response);
return response;
}
}
public class RetryDecorator extends WebServiceDecorator {
private int maxRetries;
private Duration retryDelay;
public RetryDecorator(WebService service,
int maxRetries, Duration retryDelay) {
super(service);
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}
@Override
public Response execute(Request request) {
int attempts = 0;
while (attempts < maxRetries) {
try {
return super.execute(request);
} catch (Exception e) {
attempts++;
if (attempts == maxRetries) {
throw e;
}
try {
Thread.sleep(retryDelay.toMillis());
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
throw new RuntimeException(
"Failed after " + maxRetries + " retries");
}
}
4. Spring Framework Integration
4.1 Service Decorators
@Configuration
public class WebServiceConfig {
@Bean
public WebService basicWebService() {
return new BasicWebService();
}
@Bean
public WebService loggingWebService(
@Qualifier("basicWebService") WebService service) {
return new LoggingDecorator(service);
}
@Bean
public WebService cachingWebService(
@Qualifier("loggingWebService") WebService service) {
return new CachingDecorator(service);
}
@Bean
@Primary
public WebService retryingWebService(
@Qualifier("cachingWebService") WebService service) {
return new RetryDecorator(service, 3,
Duration.ofSeconds(1));
}
}
@Service
public class WebServiceClient {
private final WebService webService;
public WebServiceClient(WebService webService) {
this.webService = webService;
}
public Response executeRequest(Request request) {
return webService.execute(request);
}
}
4.2 HTTP Request Decorators
@Component
public class RequestLoggingInterceptor
extends HandlerInterceptorAdapter {
private static final Logger logger =
LoggerFactory.getLogger(
RequestLoggingInterceptor.class);
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
logger.info("Before handling request: " +
request.getRequestURI());
return true;
}
@Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
logger.info("After handling request: " +
request.getRequestURI());
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestLoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(
InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor);
}
}
5. Testing Decorator Pattern
5.1 Unit Testing
@ExtendWith(MockitoExtension.class)
class WebServiceDecoratorTest {
@Mock
private WebService mockService;
@Test
void whenLoggingDecorator_thenLogMessages() {
// Given
WebService decorator = new LoggingDecorator(
mockService);
Request request = new Request("test");
Response expectedResponse = new Response("test");
when(mockService.execute(request))
.thenReturn(expectedResponse);
// When
Response actualResponse = decorator.execute(
request);
// Then
assertEquals(expectedResponse, actualResponse);
verify(mockService).execute(request);
}
@Test
void whenCachingDecorator_thenCacheResponse() {
// Given
WebService decorator = new CachingDecorator(
mockService);
Request request = new Request("test");
Response expectedResponse = new Response("test");
when(mockService.execute(request))
.thenReturn(expectedResponse);
// When
Response firstResponse = decorator.execute(
request);
Response secondResponse = decorator.execute(
request);
// Then
assertEquals(expectedResponse, firstResponse);
assertEquals(expectedResponse, secondResponse);
verify(mockService, times(1)).execute(request);
}
}
5.2 Integration Testing
@SpringBootTest
class WebServiceClientIntegrationTest {
@Autowired
private WebServiceClient client;
@Test
void whenExecuteRequest_thenSuccess() {
// Given
Request request = new Request("test");
// When
Response response = client.executeRequest(
request);
// Then
assertNotNull(response);
}
}
6. Lợi Ích và Nhược Điểm
6.1 Lợi Ích
- Mở rộng chức năng linh hoạt
- Tuân thủ Single Responsibility Principle
- Thay đổi hành vi trong runtime
- Kết hợp nhiều hành vi
- Không phụ thuộc vào kế thừa
6.2 Nhược Điểm
- Khó loại bỏ decorator cụ thể
- Thứ tự decorator có thể quan trọng
- Có thể tạo ra nhiều lớp nhỏ
- Code có thể phức tạp hơn
7. Best Practices
7.1 Interface Segregation
public interface Loggable {
void log(String message);
}
public interface Cacheable {
void cache(String key, Object value);
Object getFromCache(String key);
}
public class LoggingDecorator extends WebServiceDecorator
implements Loggable {
@Override
public void log(String message) {
System.out.println("Log: " + message);
}
@Override
public Response execute(Request request) {
log("Before execution");
Response response = super.execute(request);
log("After execution");
return response;
}
}
public class CachingDecorator extends WebServiceDecorator
implements Cacheable {
private Map<String, Object> cache = new HashMap<>();
@Override
public void cache(String key, Object value) {
cache.put(key, value);
}
@Override
public Object getFromCache(String key) {
return cache.get(key);
}
@Override
public Response execute(Request request) {
String key = request.getKey();
Object cached = getFromCache(key);
if (cached != null) {
return (Response) cached;
}
Response response = super.execute(request);
cache(key, response);
return response;
}
}
7.2 Factory Method
public class WebServiceDecoratorFactory {
public static WebService createDecoratedService(
WebService service,
boolean enableLogging,
boolean enableCaching,
boolean enableRetry) {
WebService decorated = service;
if (enableLogging) {
decorated = new LoggingDecorator(decorated);
}
if (enableCaching) {
decorated = new CachingDecorator(decorated);
}
if (enableRetry) {
decorated = new RetryDecorator(
decorated, 3, Duration.ofSeconds(1));
}
return decorated;
}
}
@Configuration
public class WebServiceConfig {
@Bean
public WebService decoratedWebService() {
return WebServiceDecoratorFactory
.createDecoratedService(
new BasicWebService(),
true, // enableLogging
true, // enableCaching
true // enableRetry
);
}
}
8. Common Issues và Solutions
8.1 Order Dependencies
public class OrderAwareDecorator extends WebServiceDecorator {
private int order;
public OrderAwareDecorator(
WebService service, int order) {
super(service);
this.order = order;
}
public int getOrder() {
return order;
}
}
public class DecoratorChainBuilder {
private List<OrderAwareDecorator> decorators =
new ArrayList<>();
private WebService service;
public DecoratorChainBuilder(WebService service) {
this.service = service;
}
public DecoratorChainBuilder addDecorator(
OrderAwareDecorator decorator) {
decorators.add(decorator);
return this;
}
public WebService build() {
decorators.sort(
Comparator.comparingInt(
OrderAwareDecorator::getOrder));
WebService decorated = service;
for (OrderAwareDecorator decorator : decorators) {
decorated = decorator;
}
return decorated;
}
}
8.2 Performance Optimization
public class LazyLoadingDecorator extends WebServiceDecorator {
private volatile WebService decoratedService;
private final Supplier<WebService> serviceSupplier;
public LazyLoadingDecorator(
Supplier<WebService> serviceSupplier) {
super(null);
this.serviceSupplier = serviceSupplier;
}
private WebService getDecoratedService() {
if (decoratedService == null) {
synchronized (this) {
if (decoratedService == null) {
decoratedService = serviceSupplier.get();
}
}
}
return decoratedService;
}
@Override
public Response execute(Request request) {
return getDecoratedService().execute(request);
}
}
public class BufferedDecorator extends WebServiceDecorator {
private Queue<Request> requestBuffer;
private int bufferSize;
public BufferedDecorator(
WebService service, int bufferSize) {
super(service);
this.bufferSize = bufferSize;
this.requestBuffer = new LinkedList<>();
}
@Override
public Response execute(Request request) {
requestBuffer.offer(request);
if (requestBuffer.size() >= bufferSize) {
return processBatch();
}
return null; // Or return default response
}
private Response processBatch() {
List<Response> responses = new ArrayList<>();
while (!requestBuffer.isEmpty()) {
Request request = requestBuffer.poll();
responses.add(super.execute(request));
}
return mergeBatchResponses(responses);
}
private Response mergeBatchResponses(
List<Response> responses) {
// Merge logic
return new Response("Batch processed");
}
}
9. References
9.1 Books
- Design Patterns: Elements of Reusable Object-Oriented Software
- Head First Design Patterns
- Clean Architecture by Robert C. Martin
- Patterns of Enterprise Application Architecture