본문 바로가기
후기

객체 꾸미기 (데코레이터 패턴)

by jucheol 2025. 6. 9.

디자인 원칙

개방 폐쇄 원칙(OCP - Open Closed Principle)

  • 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다

데코레이터 패턴(Decorator Pattern)

  • 객체에 추가 요소를 동적으로 추가 가능(상속이 아닌 객체를 감싸는 방식으로 기능 추가 & 변경)
  • 이 패턴을 사용하면 서브 클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있다

패턴 구조

  • Component : 기본 기능을 정의하는 인터페이스로 데코레이터와 구체적인 컴포넌트가 이를 구현한다. 따라서 컴포넌트는 원본 객체와 데코레이터를 묶는 역할을 한다.
  • ConcreteComponent : 기본 기능을 실제로 구현하는 구체적인 컴포넌트로 데코레이터 패턴의 시작점이 된다.
  • Decorator : 컴포넌트를 감싸는 추상 클래스나 인터페이스로 새로운 기능을 추가하거나 기존 기능을 수정하기 위한 메서드를 선언한다. 데코레이터는 Component를 멤버 변수로 가진다.
  • ConcreteDecorator : 새로운 기능이나 기존 기능을 수정하는 역할을 하는 구체적인 클래스다. 데코레이터 인터페이스를 구현한다.

특징

  • 데코레이터의 슈퍼 클래스는 자신이 장식하고 있는 객체의 슈퍼 클래스와 같다
  • 한 객체를 여러 개의 데코레이터로 감쌀 수 있다
  • 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기에 원래 객체(싸여 있는 객체)가 들어갈 자리에 데코레이터 객체를 넘어도 상관 없다
  • 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있다
  • 객체는 언제든지 감쌀 수 있기 때문에 실행 중에 필요한 데코레이터 마음대로 적용 가능

단점

  • 잡다한 클래스가 많아진다
  • 구성요소를 초기화하는 데 필요한 코드가 훨씬 복잡해진다
  • 특정 형식에 의존하는 코드에 데코레이터를 그냥 적용할 수 없다

예제

public abstract class Beberage {
	String description = "제목 없음";
	
	public String getDescription() {
		return description;
	}
	
	public abstract double cost();
}
 
public abstract class CondimentDecorator extends Beverage {
	Beverage beverage;
	public abstract String getDescription();
}
 
public class Espresso extends Beverage {
	public Espresso() {
		description = "에소프레소";
	}
	
	public double cost() {
		return 1.99;
	}
}
 
public class HouseBlend extends Beverage {
	public HouseBlend() {
		description = "하우스 블렌드 커피";
	}
	
	public double cost() {
		return .89;
	}
}
 
public class Mocha extends CondimentDecorator {
	public Mocha(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public String getDescription() {
		return beverage.getDescription() + ", 모카";
	}
	
	pulibc double cost() {
		return beverage.cost() + .20;
	}
}
 
public class Soy extends CondimentDecorator {
	public Soy(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public String getDescription() {
		return beverage.getDescription() + ", 두유";
	}
	
	pulibc double cost() {
		return beverage.cost() + .15;
	}
}
 
public class Whip extends CondimentDecorator {
	public Whip(Beverage beverage) {
		this.beverage = beverage;
	}
	
	public String getDescription() {
		return beverage.getDescription() + ", 휘핑크림";
	}
	
	pulibc double cost() {
		return beverage.cost() + .10;
	}
}
 
public class StarbuzzCoffee {
	public static void main(String args[]) {
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDescription() + " $" + beverage.cost());
		
		Beverage beverage2 = new DarkRost();
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

		Beverage beverage3 = new HouseBlend();
		beverage3 = new Soy(beverage3);
	  beverage3 = new Mocha(beverage3);
	  beverage3 = new Whip(beverage3);
	  System.out.println(beverage3.getDescription() + " $" + beverage3.cost());

	}
}
 
💡

// 결과

에소프레소 $1.99

다크 로스트 커피, 모카, 모카, 휘핑크림 $1.49

하우스 블렌드 커피, 두유, 모카, 휘핑크림 $1.34

적용 예시

  • 자바 I/O
Create sub-issue
 
👍1