Decorator Parttern이란?
특정 클래스들에 객체들이 할 수 있는 기능들이 있을 때,
사용자가 원하는대로 동적으로 조합할 수 있게 해주는 구조적 디자인 패턴이다.
객체에 기능들을 장식처럼 계속 붙일 수 있는 패턴이다.
Decorator Pattern을 쓰는 이유(==기존 개발 형태에 대한 문제점)?
로그를 남기는 로그시스템을 다음과 같이 하나의 클래스에 구현했다고 생각해보자.
public class Logger {
public void log(String message) {
// 기본 로그 기능
System.out.println("Logging: " + message);
// 이메일 전송
sendEmail(message);
// SMS 전송
sendSMS(message);
// 네트워크 전송
logToNetwork(message);
}
private void sendEmail(String message) {
System.out.println("Sending email: " + message);
}
private void sendSMS(String message) {
System.out.println("Sending SMS: " + message);
}
private void logToNetwork(String message) {
System.out.println("Logging to network: " + message);
}
}
여기서 다른 로그시스템을 추가하면 우리는 Logger 클래스안에 코드를 변경해야한다. > OCP 법칙 위배.
해당 클래스에 많은 기능이 존재함다. > SRP 법칙 위배
문제점
- 기능 확장의 어려움 (OCP 원칙 위반):
- 새로운 로그 전송 방식(예: 새로운 메시징 서비스)을 추가하려면, 기존 클래스의 코드를 수정해야 함.
- 클래스의 복잡성 증가:
- 모든 로그 전송 기능이 하나의 클래스에 포함되므로, 클래스가 복잡해짐.
- 단일 책임 원칙 위반:
- 클래스가 로그 메시지를 출력하는 책임 외에도 이메일, SMS, 네트워크 전송의 책임을 가지고 있음.
- 코드 중복:
- 다른 곳에서도 로그 메시지를 전송해야 할 때, 동일한 코드를 반복하게 돼.
Decorator Pattern( 패턴) 구현 방법
- 추상 클래스를 사용하는 방법
- 인터페이스 정의
- 기본 구현 클래스
- 추상 데코레이터 클래스 (일반 클래스로 구현해도 되지만 그것은 상황에 따라)
- 구체적인 데코레이터 클래스
- 추상 클래스 없이 인터페이스와 그것을 구현한 클래스들만 사용하는 방법
- 인터페이스 정의
- 기본 구현 클래스
- 기본 구현 클래스와 같은 인터페이스를 구현하는 데코레이터 클래스
로그를 남기는 로그 시스템을 데코레이터 패턴으로 변경해보자.
추상 클래스를 사용하는 방법
인터페이스 정의
public interface Logger {
void log(String message);
}
기본 구현 클래스
public class BasicLogger implements Logger {
@Override
public void log(String message) {
// 기본 로그 동작
System.out.println("Basic Logger: " + message);
}
}
추상 데코레이터 클래스
public abstract class LoggerDecorator implements Logger {
protected Logger logger;
public LoggerDecorator(Logger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
logger.log(message);
}
}
구체적인 데코레이터 클래스들이 상속을 받아야하는 상위 클래스이다.
구체적인 데코레이터 클래스
public class FileLogger extends LoggerDecorator {
public FileLogger(Logger logger) {
super(logger);
}
@Override
public void log(String message) {
super.log(message);
logToFile(message);
}
private void logToFile(String message) {
System.out.println("Logging to file: " + message);
}
}
public class ConsoleLogger extends LoggerDecorator {
public ConsoleLogger(Logger logger) {
super(logger);
}
@Override
public void log(String message) {
super.log(message);
logToConsole(message);
}
private void logToConsole(String message) {
System.out.println("Logging to console: " + message);
}
}
추상 클래스 없이 인터페이스와 그것을 구현한 클래스들만 사용하는 방법
인터페이스 정의
public interface Logger {
void log(String message);
}
기본 구현 클래스
public class BasicLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Basic Logger: " + message);
}
}
기본 구현 클래스와 같은 인터페이스를 구현하는 데코레이터 클래스
public class EmailLogger implements Logger {
private Logger logger;
public EmailLogger(Logger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
logger.log(message);
sendEmail(message);
}
private void sendEmail(String message) {
System.out.println("Sending email notification: " + message);
}
}
public class SMSLogger implements Logger {
private Logger logger;
public SMSLogger(Logger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
logger.log(message);
sendSMS(message);
}
private void sendSMS(String message) {
System.out.println("Sending SMS notification: " + message);
}
}
클라이언트 코드
public class Client {
public static void main(String[] args) {
// 체이닝을 통해 여러 데코레이터를 한 줄로 연결
Logger logger = new NetworkLogger(
new SMSLogger(
new EmailLogger(
new BasicLogger()
)
)
);
// 로그 메시지 전송
logger.log("Hello World!");
// Output:
// Basic Logger: Hello World!
// Sending email notification: Hello World!
// Sending SMS notification: Hello World!
// Logging to network: Hello World!
}
}
어떻게 구현했던지 클라이언트 코드에서는 다음과 같이 확인할 수 있다.
여기서 추상 클래스를 쓰는 차이가 명확하게 없는데 별도에 데코레이터 클래스들에게 공통 기능을 하나 더 부여하고 싶으면 추상클래스를 쓰는 이점이 있다.
다음과 같이 시간이 얼마나 걸리는지 공통 기능을 부여한다고 했을 때 추상클래스를 쓰는데 이점이 존재한다.
addTimestamp 공통 기능 추가
public abstract class LoggerDecorator implements Logger {
protected Logger logger;
public LoggerDecorator(Logger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
logger.log(message);
addTimestamp();
}
private void addTimestamp() {
System.out.println("Timestamp: " + System.currentTimeMillis());
}
}
public class EmailLogger extends LoggerDecorator {
public EmailLogger(Logger logger) {
super(logger);
}
@Override
public void log(String message) {
super.log(message);
sendEmail(message);
}
private void sendEmail(String message) {
System.out.println("Sending email notification: " + message);
}
}
public class SMSLogger extends LoggerDecorator {
public SMSLogger(Logger logger) {
super(logger);
}
@Override
public void log(String message) {
super.log(message);
sendSMS(message);
}
private void sendSMS(String message) {
System.out.println("Sending SMS notification: " + message);
}
}
public class NetworkLogger extends LoggerDecorator {
public NetworkLogger(Logger logger) {
super(logger);
}
@Override
public void log(String message) {
super.log(message);
logToNetwork(message);
}
private void logToNetwork(String message) {
System.out.println("Logging to network: " + message);
}
}
그러나 추상 클래스 없이 바로 구체적인 클래스로만 구현한 경우 addTimestamp 메서드를 들고 다녀야할 것이다. (코드 중복)
Decorator Pattern의 장단점
장점
- 기능의 유연한 확장
- 객체의 기본 기능을 변경하지 않고도, 다양한 기능을 조합하여 새로운 기능을 동적으로 추가할 수 있음
- 유지보수성 향상
- 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있어서 유지보수성도 향상됨.
- SRP 단일 책임 원칙 준수
- 각 데코레이터 클래스는 특정 기능을 추가(혹은 변경) 하는 역할을 담당하므로, 단일 책임 원칙을 준수함.
- 그러나 1번 2번자체는 OCP를 준수한다고 볼 수 있음.
단점
- 복잡성 증가
- 많은 데코레이터가 중첩되며, 코드가 복잡해지고 이해하기 어려워질 수 있음.
- 런타임 비용 증가
- 여러 데코레이터를 중첩하여 사용하면, 각 데코레이터에서 메서드 호출이 연속에서 일어나기 때문에 런타임 비용이 증가할 수 있음.
Decorator Pattern의 사용 사례
- Java I/O 라이브러리
- java.io 패키지의 다양한 스트림 클래스가 데코레이터 패턴을 사용하여 기능을 확장함
- FileInputStream, BufferedInputStream, DataInputStream
- java.io 패키지의 다양한 스트림 클래스가 데코레이터 패턴을 사용하여 기능을 확장함
- GUI 프레임워크
- 그래픽 사용자 인터페이스 요소에 스크롤 바, 테두리, 색상 변경 등의 기능을 동적으로 추가할 때 사용됨.
- 게임 개발
- 게임 캐릭터에 대한 다양한 능력이나 무기를 동적으로 추가할 때 사용됨.
반드시 인터페이스로만 시작하지 않고도 다음과 같이 데코레이터 패턴에 대해서 이해하면 좋을 듯 싶다.
https://www.youtube.com/watch?v=UTmY_oB4V8I
반응형
'CS > 디자인패턴' 카테고리의 다른 글
[Design Pattern] Composite Pattern(컴포지트 패턴) (0) | 2024.06.06 |
---|---|
[Design Pattern] Facade Pattern(퍼사드 패턴) (0) | 2024.06.05 |
[Design Pattern] Proxy Pattern(프록시 패턴) (0) | 2024.06.03 |
[Design Pattern] Strategy Pattern(전략 패턴) (0) | 2024.06.02 |
[Design Pattern] Factory Pattern(팩토리 패턴) (0) | 2024.06.01 |