본문 바로가기
소프트웨어공학/디자인 패턴

Decorator Pattern - 구조, 구성과 위임을 통해 특정 개체에 행동을 동적으로 추가 | [Design pattern] 디자인 패턴

by javapp 자바앱 2021. 12. 11.
728x90

 

 

Decorator Pattern

 

같은 class의 다른 객체들에는 영향을 주지 않고,

구성과 위임을 통해 특정 객체에 행동을 동적으로 추가

 

 

  • 소스를 변경하지 않고 기능을 확장
  • 기존 객체의 메소드에 새로운 행동을 추가하거나 오버라이딩 가능
  • 객체에 독립적인 기능을 계속해서 추가, 확장할 때 유용
  • 객체에 부가적인 기능을 동적으로 추가할 때 사용

 

 

문제점)

  • 상속을 사용한 객체의 기능 확장은 subclass를 양산한다.
  • Subclassing을 사용한 정적 확장의 문제점:

   ① 너무 많은 subclass가 필요함

   ② 새로운 유형의 음료수가 개발될 때마다 subclass계속 추가되어야 함.

 

 

패턴 아이디어)

	DataInputStream dis; // primitive value
 	dis = new DataInputStream( 
			new BufferedInputStream(
		  		new FileInputStream(dataFile)
			)
   	  	);

사용자는 connect stream 객체를 filter stream(장식) 객체들로 포장(랩핑)하여

원하는 기능을 가진 스트림 객체를 동적으로 만들어낸다.

 


 

실례)

윈도우 기능이나 I/O Stream

java 라이브러리 개발에도 많이 쓰인다.

 

 

 

Wrapping을 통해 객체의 책임을 동적으로 확장

: 바이트 입력 + 버퍼링 + 타입 변환

 

 

 

 

//기본 객체:
	FileInputStream fis = new FileInputStream(dataFile);	

//장식 객체들:
	BufferedInputStream bis = new BufferedInputStream( fis ); 	//⇦ COMPOSITION of fis
	DataInputStream dis     = new DataInputStream( bis );		//⇦ COMPOSITION of bis

구성과 위임을 통해 추가적인 연산들을 실행

 

사용자는 connect stream 객체를 filter stream 객체들로 포장하여

원하는 기능을 가진 스트림 객체

 파라미터를 통해 객체(기능)을 동적 추가(확장)

 


 

Java I/O Streams

 

Java I/O Decorator 패턴의 downsides 중 하나이다.

장식 객체는 input stream으로 uppercase에서 lowercase로 변환한다.

 

 

Decorator Pattern Diagram

 

다이어그램

composite pattern 유사

DIP 적용

 

Component             : 추상화 클래스

ConcreteComponent : 기본 구현 클래스

 

Decorator                : 장식(옵션) 추상 클래스, 최상위 추상화 클래스 상속, 상위 클래스 구성

ConcreteDecorator    : 기본 클래스에 행동을 추가 구현

 

 

 


 

 

구현 예제

 

 

  • 추상클래스-추상클래스-구상클래스
  • 인터페이스-추상클래스-구상클래스

       (계약, 코드 공유(분류), 구상)

 

 

 

추상클래스 활용

 

Component

public abstract class Beverage
{
    String description = "Unknown Beverage";

    public String getDescription(){
        return description;
    }
    public abstract double cost();
}

 

ConcreteComponent

public class Espresso extends Beverage
{
    public Espresso() {
        description = "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

 

Decorator

public abstract class CondimentDecorator extends Beverage 
{
    Beverage beverage;
    public CondimentDecorator (Beverage beverage) {
        this.beverage = beverage; //객체 구성
    }
    public abstract String getDescription();
}

 

ConcreteDecorator

public class Milk extends CondimentDecorator 
{
    Beverage beverage;

    public Milk(Beverage beverage) {
        super(beverage);
        this.beverage = beverage; //객체 구성
    }

    public String getDescription() {
        return beverage.getDescription() + ", Milk";
    }

    public double cost() {
        return .10 + beverage.cost();
    }
}

 

메인 실행문

public class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription()
                + " $" + beverage.cost());

        Beverage beverage2 = new Espresso();
        beverage2 = new Milk(beverage2);
        System.out.println(beverage2.getDescription()
                + " $" + beverage2.cost());
    }
}

 

실행결과

 

 


 

 

인터페이스 활용

 

// Revised : 추 - 추 - 구상
// cf : interface - abstract - concrete
// Component
public interface Beverage
{
    public String getDescription();

    public double cost();
}

 

// 첨가물
// Decorator
public abstract class CondimentDecorator implements Beverage // Component
{
    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
}

 

// ConcreteComponent
public class Espresso implements Beverage // Component
{
    private String description = "Espresso";
    private double cost = 1.99;

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double cost() {
        return cost;
    }
}


public class HouseBlend implements Beverage
{
    private String description = "HouseBlend";
    private double cost = 1.99;

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double cost() {
        return cost;
    }
}

 

// ConcreteDecorator
public class Milk extends CondimentDecorator
{
    private String description = " | Milk";

    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription()+description;
    }

    @Override
    public double cost() {
        return .1+beverage.cost();
    }
}

public class Mocha extends CondimentDecorator
{
    private String description = " | Mocha";

    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription()+description;
    }

    @Override
    public double cost() {
        return beverage.cost() + .5;
    }
}

 

 


 

 

Detail

Decorator 객체와 함께 생성

 

 

Beverage 를 상속한 DarkRoast(음료 메뉴) 객체로 시작

 

 

 

사용자는 Mocha를 원한다.

그래서 Mocha 객체를 생성하고

DarkRoast 객체에 씌운다.

 

 

고객은 휘핑 크림을 원한다.

 

그래서 휘핑 크림 객체를 생성하고

Mocha 객체에 씌운다.

 

 

 

Whip cost()는 각 객체의 위임을 통해 실행된다.

 

 

 

 

 

 

 

 

 

 

 

ß Decorators have the same supertype as the objects they decorate.

 

ß You can use one or more decorators to wrap an object.

 

ß Given that the decorator has the same supertype as the object it decorates, we can pass

around a decorated object in place of the original (wrapped) object.

 

ß The decorator adds its own behavior either before and/or after delegating to the object it

decorates to do the rest of the job.

 

ß Objects can be decorated at any time, so we can decorate objects dynamically at runtime

with as many decorators as we like.

 

 

 

 

 

책 참고 : Head First Design Patterns

댓글