osul_world 2021. 12. 29. 20:31
728x90

 

장식자 패턴


 

  • 객체에 동적으로 새로운 행위를 추가할 수 있도록 해주는 장식 패턴

상속과 포함을 모두 사용하여 구현한다.

 

[장식자 패턴 사용 환경]

상속을 통해 객체의 책임을 확장하기 어려울 경우

실행시간에 객체의 행위를 추가하고싶다.

객체에 추가할 수 있는 서로 독립적인 다양한 기능들이 존재한다.

자유롭게 객체에 기능을 추가하고 제거하고 싶을 경우

[장점]

실행시간에 유연하게 객체에 기능 상태를 추가 제거 할수있다.

클래스의 높은 응집력(SRP)

[단점]

클래스가 많아질수있으며 디버깅이 힘들어질수있다.

특정 제거와 제약이 힘들수있다.

[관련패턴]

DIP 생성패턴

 

  • 클래스 폭발 문제를 완화시킬수있다.

커피같이 첨가물이 포함되는 주문 시스템을 만든다고 할때 비슷한 클래스가 무수히 늘어나는 현상

커피+초코추가 , 커피+우유추가 , 커피+쿠키추가 , 커피+우유+쿠키추가 등

비슷한 클래스가 엄청나게 발생

image

해결시도 1. 모든 추가옵션 맴버로 유지

image

안쓰는 추가옵션은 의미없는 선언 = 메모리낭비 ISP위배

변경발생시 관련코드 모두 직접수정해야함 OCP위배

추가사항이 적합하지 않을수있음 LSP위배

해결시도 2. 상속

행위는 컴파일시간에 고정, 실행시간에 동적으로 추가 불가 또한 모든 자식 클래스는 동일한 행위를 무조건 상속받아야 함

수정발생시 슈퍼클래스를 수정해야함

해결시도 3. 상속 + 포함이용 = ''데코레이트 패턴''

has-a: 객체에 행위를 실행시간에 추가할 수 있음

상속: 장식된 객체를 원래 객체 대신하여 사용할수있도록

image

장식 대상 추상 클래스 또는 interface:

장식된 객체를 조작할 때 사용하는 리모컨

때문에 장식자들도 이를 상속해야한다.

장식자 추상클래스:

꼭 필요하진 않을수있지만 재정의 명시기능과 장식자와 장식 대상자를 구분할수있는 구분자로 활용가능

장식자 구체화 클래스:

장식 대상을 has-a로 유지

장식대상은 장식되어있을수도있다. 장식대상 추상클래스를 최상위 공통리모컨 타입으로 이용 하기때문에 장식여부에 상관없이 has-a로 받을수있어야한다.

 

[코드]

public abstract class Beverage {
 private String description = "이름없는 음료";

 public void setDescription(String description) {
     this.description = description;
 }

 public String getDescription() {
     return description;
 }

 public abstract int cost();
}
/**
Beverage class

가장 상위의 슈퍼 클래스인 Beverage 클래스 입니다. 음료의 공통적인 성질을 따로 뺀 것으로 카페에서 판매하는 모든 음료 및 첨가물은 이 클래스를 상속 받아야 합니다.
*/
public abstract class CondimentDecorator extends Beverage {

    //abstract cost(); // CondimentDecorator도 추상 클래스라 Beverage 추상 메서드 재정의할 필요 X

    public abstract String getDescription(); 
 //Beverage getDescription을 추상 메서드로 변경
 //첨가물 구현체들에게 각각 overriding 위임
}
/**
CondimentDecorator class

모든 첨가물들이 상속 받아야 하는 클래스 입니다.
*/

 

public class DarkRoast extends Beverage {
	private static final int COST = 1200;

	public DarkRoast(){
		setDescription("다크로스트 커피"); //super.setDescription
	}

	@Override
	public int cost() {
		return COST;
	}
}
/**
판매하는 음료: "장식될 객체"
*/
  • 꾸며질 객체
// CondimentDecorator + Beverage 상속
//장식자임을 CondimentDecorator를 통해 확인가능
public class Whip extends CondimentDecorator { 

	private Beverage beverage;
	public Whip(Beverage beverage){
		this.beverage = beverage;
	}

	@Override
	public String getDescription() { //이름 장식
		return beverage.getDescription()+", 크림";
	}
	@Override
	public int cost() { //가격 장식
		return beverage.cost()+500;
	}
}
/**
첨가물: 장식될 객체
첨가물 각각 메서드들을 생성하여 다양한 구현을 할수있다.
*/
  • 장식할 객체: 꾸며질 객체를 감싼다.

 

[메인]

//클라이언트가 사용할 메인 테스트 코드
public class CoffeeTest {
	public static void main(String[] args) {

		Beverage beverage = new DarkRoast();
 		//공통리모컨 abstract beverage 사용
         // 피장식자 DarkRoast 주입


		beverage = new Mocha(beverage);
        //DarkRoast 객체에 Mocha 장식자 덧씌우기
		beverage = new Milk(beverage);
        //DarkRoast+Mocha 객체에 Milk 장식자 덧씌우기


		System.out.printf("%s: %,d원%n", 
			beverage.getDescription(), beverage.cost()); 
        //장식된 객체를 원래 객체를 대신하여 사용 가능
	}
}
  • 메인코드

 

[객체가 꾸며지는 모습]

image

 

 

  • 데코레이터 패턴을 이용하지않고 추가객체들을 컬렉션 리스트로 유지할 수 도있다.

다만 데코레이터 패턴은 포장과정에서 메서드를 다양하게 재정의 하여 다채로운 구현이 가능하다.

복잡한 방식이 필요한 경우 ''데코레이터 패턴''이 ''컬렉션 리스트''를 사용하는것보다 유리할것

 

  • 제한이나 제약조건이 복잡한 경우 생성패턴으로 설계하는게 유리할수있다.

데코레이션 패턴을 적용하면 제약조건을 부여할때 코드중복,응집력약화 등의 문제가 발생한다.

이땐 데토레이터 패턴보단 생성패턴을 활용해 설계하는게 더 좋은 방법이 될수있다.

728x90