북극곰의 개발일기

데코레이터 패턴





posted by purplebeen on Tue Jan 30 2018 23:10:10 GMT+0900 (KST) in JAVA


객체지향 디자인 원칙

  1. 바뀌는 부분은 캡슐화한다.
  2. 상속보다는 구성을 활용한다.
  3. 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
  4. 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.
  5. 클래스는 확장에 대해서는 열려 있지만 변경에 대해서는 닫혀 있어야 한다. (OCP, Open-Closed-Principle)

데코레이터 패턴 : 객체에 추가 요소를 동적으로 더할 수 있다. 데코레이털를 사용하면 서브 클래스를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있다.

데코레이터 패턴

  • 상속을 통해 확장을 할 수도 있지만, 디자인의 유연성 면에서 보면 별로 좋지 않다.
  • 기존 코드를 수정하지 않고도 행동을 확장하는 방법이 필요하다.
  • 구성과 위임을 통해서 실행중에 새로운 행동을 추가할 수 있다.
  • 상속 대신 데코레이터 패턴을 통해서 행동을 확장할 수 있다.
  • 데코레이터 패턴에서는 구상 구성 요소를 감싸주는 데코레이터를 사용한다.
  • 데코레이터 클래스의 형식은 그 클래스가 감싸고 있는 클래스의 형식을 반영한다 (상속 또는 인터페이스 구현을 통해서 자신이 감살 클래스와 같은 형식을 가지게 된다.)
  • 데코레이터에서는 자기가 감싸고 잇는 구성요소의 메소들르 호출한 결과에 새로운 기능을 더함으로써 행동을 확장한다.
  • 구성 요소를 감싸는 데코레이터의 개수는 제한이 없습니다.
  • 구성요소의 클라이언트 입장에서는 데코레이터의 존재를 알 수 없다. (단, 클라이언트에서 구체적인 형식에 의존하게 되는 경우는 예외)
  • 데코레이터 패턴을 사용하면 자잘한 객체들이 매우 많이 추가될 수 있지만, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수 있다.

예를 들어, 어떤 카페에서 주문하는 프로그램을 만든다고 생각해 보자.

각 요소들이 필요로 하는 것이 가격을 계산하는 cost() 메소드와 제품에 대한 설명을 하는

description 인스턴스변수라고 할때, 만약 이것을 단순히 상속을 통해서 구현한다고 하면

각각 경우의 수에 따라 너무 많은 하위클래스들이 생성되어 이를 관리하기가 어려워질 것이다.

이를 해결하기 위해 cost() 메소드를 구현하고 super 키워드를 통해 처리한다고 해도 여러 옵션이 추가된다는 지

등의 예외가 발생할때마다 코드를 일일이 수정해줘야 한다.

이를 해결하기 위해 등장한것이 데코레이션 패턴이다.

자 그러면 이제 이 프로그램을 데코레이션 패턴에 맞추어서 짜 보자.

package decorationPattern;

public abstract class Beverage {
	String description = "제목 없음";
	
	public String getDescription() {
		return description;
	}
	
	public abstract double cost();

}

각각 항목마다 가격이 달라지기 때문에 cost()는 추상 메소드로 선언하였고

외부에서 description의 값을 가져올 수 있도록 description에 대한 getter 메소드를 만들었다.

자 이제 그러면 이를 구현하는 컴포넌트를 만들어보자.

package decorationPattern;

public class Espresso extends Beverage {
	
	public Espresso() {
		description = "에스프레소";
	}
	@Override
	public double cost() {
		return 1.99;
	}

}

description을 에스프레소라는 값으로 초기화하고,

cost()를 오버라이딩하여 에스프레소의 값인 1.99를 리턴시키고 있다.

이렇게만 주문이 끝난다면 얼머나 좋을까 싶지만. 사용자가 추가할 수 있는 옵션들이 있다.

이 옵션들은 필수적으로 들어가는 것이 아니라 사용자에 따라서 유동적으로 변할 수 있는 것이기 때문에

데코레이터로 처리하는 것이 좋을 것이다.

package decorationPattern;

public abstract class CondimentDecorator extends Beverage {
	public abstract String getDescription();
}

abstract 타입의 데코레이터 클래스이다. Beverage를 상속받아 행동 형태를 맞춰 주었고, 변화된 Desctiption의 행동을 제어하기 위해

getDescription()을 abstract method 형태로 생성하였다. 자 이제 그러면 이를 구현하는 Decorator를 셍성해보자.

package decorationPattern;

public class Mocha extends CondimentDecorator {
	Beverage beverage;
	public Mocha(Beverage beverage) {
		// TODO Auto-generated constructor stub
		this.beverage = beverage;
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription() + " , 모카";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return .20 + beverage.cost();
	}

}

모카를 추가하는 데코레이터이다.

getDescription method를 오버라이딩하여 객체 생성시 인자값으로 받아온 beverage 형태의 객체의 getdescription()을 실행한뒤 ", 모카"를 추가하여 리턴해주고,

cost()메소드를 오버라이딩하여 모카의 값인 0.30에 객체 생성시 인자값으로 받아온 객체의 cost를 더해 리턴 시킨다.

자 그러면 이제 이런 데코레이터를 실제로 호출해서 데코레이터들이 잘 랩핑되어 cost와 description의 값이 제대로 더해져서 나오는지 확인해보자.

package decorationPattern;

public class StarbuzzCoffee {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDescription() + " $" + beverage.cost());
		
		Beverage beverage2 = new DarkRoast();
		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 SoyMilk(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDescription()+" $" + beverage3.cost());
	}

}

객체 하나를 컴포넌트의 최상위 클래스인 Beverage 형태로 선언한 후 컴포넌트 객체로 초기화하였고,

새로운 객체를 생성하면서 그 전에 있던 객체를 인자값으로 계속 넣어주고 있다.

이를 실행시켜보면,

다크로스트, 더블모카, 휘핑 = $0.99 + $0.20 + $0.20 + $0.10 = $1.49

하우스블렌드, 두유, 모카, 휘핑 = $0.89 + $0.10 + $0.20 + $0.10 = $1.34

각 컴포넌트에 데코레이터 객체들이 잘 랩핑되어 값이 제대로 나오는 것을 알 수 있다.