반응형
1. 데코레이터 패턴 (Decorator Pattern)
1. 정의
데코레이터 패턴은 객체에 새로운 기능을 동적으로 추가할 수 있는 디자인 패턴입니다. 기존 객체를 수정하지 않고도 객체에 새로운 행동이나 책임을 부여할 수 있습니다. 이 패턴은 상속을 통한 기능 확장이 아닌, 객체를 감싸는 방식으로 추가적인 기능을 제공하는 방식입니다.
데코레이터 패턴은 주로 다양한 기능을 가진 객체를 유연하게 조합해야 할 때 사용됩니다. 상속을 통해 기능을 확장하는 대신, 객체를 데코레이터로 감싸 여러 기능을 동적으로 추가할 수 있습니다.
2. 목적
- 유연한 기능 확장: 객체를 감싸서 새로운 기능을 추가할 수 있으므로, 상속 구조보다 더 유연한 방법으로 기능을 확장할 수 있습니다.
- 기존 클래스의 수정 없이 기능 추가: 데코레이터 패턴은 기존 클래스의 코드를 변경하지 않고도 새로운 기능을 추가할 수 있습니다.
- 객체의 책임을 동적으로 변경: 실행 중에 객체의 기능을 변경하거나 추가할 수 있기 때문에 매우 유연합니다.
3. 사용 예시
데코레이터 패턴은 다음과 같은 상황에서 유용합니다:
- 클래스의 기능을 다양한 방식으로 조합할 필요가 있을 때
- 객체에 대한 기능 확장을 런타임에 동적으로 해야 할 때
- 기존 객체에 여러 책임을 덧붙여야 하지만, 이를 위해 많은 하위 클래스를 만들고 싶지 않을 때
4. 구현 방법
데코레이터 패턴은 기본적으로 인터페이스나 추상 클래스를 사용해 동일한 타입을 유지하면서, 기능을 확장하는 객체(데코레이터)가 원본 객체를 감싸는 방식으로 구현됩니다. 데코레이터는 원본 객체의 메서드를 호출하면서 그 앞뒤로 새로운 기능을 추가할 수 있습니다.
// 데코레이터 패턴을 구현한 예시
// 1. 컴포넌트 인터페이스
interface Coffee {
String getDescription();
double cost();
}
// 2. 기본 컴포넌트 (Concrete Component)
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 5.0;
}
}
// 3. 데코레이터 클래스 (추상 클래스)
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
public double cost() {
return decoratedCoffee.cost();
}
}
// 4. 구체적인 데코레이터 클래스 (Concrete Decorators)
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", Milk";
}
@Override
public double cost() {
return decoratedCoffee.cost() + 1.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", Sugar";
}
@Override
public double cost() {
return decoratedCoffee.cost() + 0.5;
}
}
public class DecoratorPatternDemo {
public static void main(String[] args) {
// 기본 커피
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.cost());
// 커피에 우유 추가
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.cost());
// 커피에 설탕 추가
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.cost());
}
}
5. 동작 설명
- Coffee 인터페이스는
getDescription()
과cost()
메서드를 정의한 컴포넌트입니다. - SimpleCoffee는 기본 커피 클래스이며,
getDescription()
은 "Simple Coffee"를 반환하고 가격은 $5.0입니다. - CoffeeDecorator는 데코레이터 클래스이며, 다른 Coffee 객체를 감쌉니다. 이 클래스는 기본적으로 원본 커피의 기능을 그대로 전달합니다.
- MilkDecorator와 SugarDecorator는 구체적인 데코레이터로, 각각 우유와 설탕을 추가하는 기능을 제공하며, 그에 따른 비용을 추가합니다.
6. 장점
- 기능 확장의 유연성: 상속을 사용하지 않고 객체의 기능을 추가하거나 변경할 수 있습니다. 데코레이터를 계속해서 조합할 수 있기 때문에 다양한 기능을 동적으로 추가할 수 있습니다.
- 기존 코드 수정 불필요: 기존 클래스의 코드를 변경하지 않고도 기능을 확장할 수 있으므로, 기존 코드의 안정성을 유지할 수 있습니다.
- 객체 간 결합도 감소: 데코레이터 패턴은 상속을 사용하지 않으므로 클래스 간 결합도가 낮습니다.
7. 단점
- 객체가 많아질 수 있음: 데코레이터 패턴을 많이 사용하면 데코레이터 객체가 많이 생길 수 있어 코드가 복잡해질 수 있습니다.
- 디버깅 어려움: 객체를 여러 데코레이터로 감쌀 경우, 디버깅 시 객체의 구조를 파악하기가 어려울 수 있습니다.
- 생성 순서가 중요: 데코레이터를 사용하는 순서에 따라 동작이 달라질 수 있기 때문에, 생성 순서를 신경 써야 합니다.
8. 데코레이터 패턴이 유용한 경우
- 객체의 기능을 동적으로 추가해야 할 때: 런타임에서 객체의 기능을 조합하거나 수정해야 할 때 유용합니다.
- 여러 조합이 필요한 경우: 여러 기능이 독립적으로 추가될 수 있고, 그 조합에 따라 동작이 달라져야 할 때 데코레이터 패턴이 적합합니다.
- 상속 대신 기능을 유연하게 확장하고 싶을 때: 상속을 사용하는 대신 객체를 감싸면서 기능을 추가하고 싶을 때 사용됩니다.
9. 실제 사용 예
- 자바 IO 패키지: 자바의
BufferedReader
,InputStreamReader
등은 데코레이터 패턴을 사용하여 기본 입력 스트림에 버퍼링 기능, 문자 변환 기능 등을 동적으로 추가합니다.
데코레이터 패턴은 상속을 사용하지 않고도 객체에 새로운 기능을 동적으로 추가할 수 있는 매우 유용한 패턴으로, 특히 객체의 다양한 기능을 조합하고 싶을 때 적합합니다.
반응형
'언어 > Java' 카테고리의 다른 글
[ Java ] Observer Pattern - 옵저버 패턴 알아보기 (3) | 2024.10.03 |
---|---|
[ Java ] Proxy Pattern - 프록시 패턴 알아보기 (1) | 2024.10.03 |
[ Java ] Builder Pattern - 빌더 패턴 알아보기 (1) | 2024.10.03 |
[ Java ] Prototype Pattern - 프로토타입 패턴 알아보기 (0) | 2024.10.03 |
[ Java ] coding pattern - 자바 대표 코딩 패턴 알아보기 (1) | 2024.10.03 |