본문 바로가기

JAVA/디자인 패턴

[Design Pattern] Decorator(데코레이터) 패턴이란?

반응형
구조 패턴(Structural Pattern)


데코레이터 패턴은 객체 지향 디자인 패턴 중 하나로, 기존 객체에 새로운 기능을 추가하거나 객체의 행동을 수정하지 않고, 객체의 기능을 동적으로 확장하는 방법을 제공합니다.

이 패턴은 객체의 기능을 각각의 작은 단위로 분리하고, 이 단위들을 조합해서 새로운 기능을 만들어내는 방법을 사용합니다. 이렇게 분리된 각각의 단위를 데코레이터(Decorator)라고 부릅니다.

데코레이터 패턴에서는 데코레이터가 기존 객체와 동일한 인터페이스를 가지며, 데코레이터는 기존 객체를 래핑(Wrapper)합니다. 이 래핑된 객체는 기존 객체의 기능을 그대로 수행하면서, 새로운 기능을 추가하거나 변경할 수 있습니다. 이렇게 데코레이터 패턴은 객체의 기능을 동적으로 확장할 수 있으며, 기존 코드 수정 없이도 새로운 기능을 추가할 수 있도록 합니다.

예를 들어, 다양한 커피 종류에 대한 주문을 처리하는 코드를 작성하고 있다고 가정해봅시다. 이때 데코레이터 패턴을 사용하여, 커피의 종류마다 추가적인 옵션을 제공하는 방법을 구현할 수 있습니다. 예를 들어, 블랙 커피에 설탕을 추가하는 옵션, 아메리카노에 휘핑 크림을 추가하는 옵션 등이 있을 수 있습니다. 각각의 옵션은 데코레이터로 구현되며, 커피 객체를 래핑하여 새로운 옵션을 추가할 수 있도록 합니다.

 

 

자바에서 데코레이터 패턴은 다양한 곳에서 사용됩니다. 그 중에서도 대표적인 예시로는 자바의 입출력 API가 있습니다.

InputStream inputStream = new FileInputStream("input.txt");
DataInputStream dataInputStream = new DataInputStream(inputStream);

int value = dataInputStream.readInt();


위 코드에서 'DataInputStream' 클래스는 'FileInputStream' 클래스를 상속받은 'InputStream' 클래스를 데코레이터로 사용합니다. 따라서 'FileInputStream'으로부터 데이터를 읽으면서, 추가적으로 엔디안 변환을 수행하여 정수 값을 읽어올 수 있습니다. 이와 같은 방식으로 'FilterOutputStream' 클래스를 이용하여 출력에 대해서도 데코레이터 패턴을 구현할 수 있습니다.

 

 

Decorator 패턴 예제

Diagrams

 

자바에서 데코레이터 패턴을 구현하는 방법은 크게 두 가지가 있습니다. 첫 번째는 인터페이스를 이용한 방법이고, 두 번째는 추상 클래스를 이용한 방법입니다. 여기서는 추상 클래스를 이용한 방법으로 예시를 들어보겠습니다.

우선, 커피 객체를 나타내는 'Coffee' 클래스를 생성합니다. 이 클래스는 기본적인 커피에 대한 정보를 가지고 있습니다.

public abstract class Coffee {
    protected String description;

    public String getDescription() {
        return description;
    }

    public abstract int cost();
}



그리고 이제 추가적인 옵션을 나타내는 데코레이터 클래스들을 생성합니다. 예를 들어, 설탕을 추가하는 옵션을 나타내는 'Sugar' 데코레이터 클래스를 생성해봅시다. 이 클래스는 'Coffee' 클래스를 상속받아 기본적인 커피에 대한 정보를 가지고 있으면서, 설탕을 추가하는 기능을 제공합니다.

public abstract class Sugar extends Coffee {
    public abstract String getDescription();
}



마찬가지로, 아메리카노에 휘핑 크림을 추가하는 옵션을 나타내는 'WhipCream' 데코레이터 클래스를 생성해봅시다.

public abstract class WhipCream extends Coffee {
    public abstract String getDescription();
}



그리고 이제 각각의 커피 종류에 대한 클래스를 생성합니다. 예를 들어, 블랙 커피에 대한 클래스를 'BlackCoffee'라는 이름으로 생성해봅시다.

public class BlackCoffee extends Coffee {
    public BlackCoffee() {
        description = "Black Coffee";
    }

    @Override
    public int cost() {
        return 3000;
    }
}



이제 데코레이터를 사용하여 추가적인 옵션을 제공하는 커피를 만들어보겠습니다. 'Sugar' 클래스를 상속받아 설탕을 추가하는 옵션을 제공하는 'SugarCoffee' 클래스를 생성합니다.

public class SugarCoffee extends Sugar {

    private Coffee coffee;

    public SugarCoffee(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return this.coffee.getDescription() + ", Sugar";
    }

    @Override
    public int cost() {
        return this.coffee.cost() + 200;
    }
}

 

 

마지막으로, 위와 같은 옵션을 제공하는 커피를 하나 더 만들어보겠습니다. 'WhipCream' 클래스를 상속받아 휘핑크림을 추가하는 옵션을 제공하는 'WhipCreamCoffee' 클래스를 생성합니다.

 

public class WhipCreamCoffee extends WhipCream {

    private Coffee coffee;

    public WhipCreamCoffee(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return this.coffee.getDescription() + ", WhipCream";
    }

    @Override
    public int cost() {
        return this.coffee.cost() + 500;
    }
}

 

 

그리고 이제 'SugarCoffee'와 'WhipCreamCoffee' 클래스를 이용하여 블랙 커피에 설탕, 휘핑크림을 추가한 새로운 커피를 만들 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Coffee blackCoffee = new BlackCoffee(); // 블랙커피 주문
        Coffee sugarCoffee = new SugarCoffee(blackCoffee); // 블랙커피 + 설탕
        Coffee whipCreamCoffee = new WhipCreamCoffee(sugarCoffee); // 블랙커피 + 설탕 + 휘핑크림

        System.out.println(whipCreamCoffee.getDescription() + " - " + whipCreamCoffee.cost() + "원");
    }
}



실행 결과는 다음과 같습니다.

Black Coffee, Sugar, WhipCream - 3700원

 

반응형