Menu
Coddy logo textTech

데코레이터 패턴

Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 72번째.

데코레이터 패턴(Decorator Pattern)은 객체들을 데코레이터라고 불리는 특별한 객체들로 감싸서 새로운 행동들을 추가할 수 있게 해주는 구조적 디자인 패턴입니다. 컴파일 타임에 행동을 추가하는 상속과 달리, 데코레이터는 원래 클래스를 수정하지 않고 런타임에 동적으로 기능을 추가합니다.

기본 커피에서 시작하여 우유, 설탕 또는 휘핑크림과 같은 추가 옵션을 더할 수 있는 커피숍을 상상해 보세요. 각 추가 옵션은 원래의 커피를 감싸며, 가격과 설명을 추가합니다. 이 패턴은 공통 인터페이스를 사용하므로 장식된 객체(decorated objects)를 원본과 동일하게 취급할 수 있습니다:

interface Coffee {
    String getDescription();
    double getCost();
}

class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

데코레이터는 래핑된 객체에 대한 참조를 유지하며, 자신의 동작을 추가하는 동시에 그 객체로 호출을 위임합니다. 여러 데코레이터를 쌓아서 기능을 결합할 수 있습니다:

Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);

System.out.println(order.getDescription());  // 일반 커피, 우유, 설탕
System.out.println(order.getCost());         // 2.75

데코레이터 패턴(Decorator Pattern)은 서브클래스를 폭발적으로 늘리지 않으면서 객체에 새로운 책임을 추가해야 할 때 그 진가를 발휘합니다. 각 데코레이터는 독립적이므로, 필요한 동작들을 자유롭게 조합하여 원하는 기능을 정확하게 구현할 수 있습니다.

challenge icon

챌린지

쉬움

데코레이터 패턴(Decorator Pattern)을 사용하여 알림 시스템을 구축해 봅시다! 데코레이터를 서로 감싸는 방식으로 이메일 알림, SMS 알림, 푸시 알림 등 다양한 전송 채널로 기능을 확장할 수 있는 기본 알림을 만들게 됩니다. 이는 하나의 메시지가 동시에 여러 채널을 통해 전송되어야 하는 실제 시스템의 모습을 반영합니다.

코드는 다음 네 개의 파일로 구성됩니다:

  • Notifier.java: 데코레이터 패턴의 컴포넌트 인터페이스 역할을 하는 Notifier 인터페이스를 정의합니다. 알림 전송을 처리하는 send(String message) 메서드를 선언해야 합니다.
  • BasicNotifier.java: Notifier를 구현하는 구체적인 컴포넌트 클래스를 생성합니다. 이는 가장 단순한 형태의 알림인 인앱(in-app) 알림을 나타냅니다. send가 호출되면 "In-App: " + message를 출력해야 합니다.
  • NotifierDecorators.java: 여기에 데코레이터 계층 구조를 구축합니다. Notifier를 구현하고 래핑된 Notifier 객체(생성자를 통해 전달됨)에 대한 참조를 보유하는 추상 클래스 NotifierDecorator부터 시작하세요.

    그런 다음 NotifierDecorator를 상속받는 세 개의 구체적인 데코레이터를 만듭니다:

    EmailDecorator - send가 호출되면 먼저 래핑된 notifier에게 위임한 다음, "Email: " + message를 출력합니다.

    SMSDecorator - send가 호출되면 먼저 래핑된 notifier에게 위임한 다음, "SMS: " + message를 출력합니다.

    PushDecorator - send가 호출되면 먼저 래핑된 notifier에게 위임한 다음, "Push: " + message를 출력합니다.

  • Main.java: 데코레이터 시스템을 하나로 합칩니다! 알림 메시지(String)와 추가할 채널의 쉼표로 구분된 목록(String)의 두 가지 입력을 받습니다. 예를 들어 "email,sms" 또는 "push,email,sms"와 같습니다.

    BasicNotifier로 시작하세요. 그런 다음 입력된 채널에 따라 나타나는 순서대로 적절한 데코레이터로 감쌉니다. 유효한 채널 이름은 "email", "sms", "push"입니다.

    마지막으로, 완전히 데코레이션된 notifier에서 메시지와 함께 send를 호출합니다.

전송할 메시지(String)와 활성화할 채널(String, 쉼표로 구분됨)의 두 가지 입력을 순서대로 받게 됩니다.

예를 들어, 입력값이 "Server is down!""email,push"인 경우 출력은 다음과 같습니다:

In-App: Server is down!
Email: Server is down!
Push: Server is down!

각 데코레이터가 원래의 알림을 유지하면서 어떻게 고유한 동작을 추가하는지 확인해 보세요. 래핑 순서가 출력 순서를 결정합니다. 가장 안쪽의 컴포넌트(BasicNotifier)가 먼저 실행되고, 각 데코레이터가 자신의 레이어를 추가합니다. 각 조합에 대해 별도의 클래스를 만들지 않고도 어떤 채널 조합이든 자유롭게 섞어서 사용할 수 있습니다!

치트 시트

데코레이터 패턴(Decorator Pattern)은 객체를 데코레이터 객체로 감싸서 새로운 동작을 추가하는 구조적 디자인 패턴입니다. 원본 클래스를 수정하지 않고 런타임에 동적으로 기능을 추가합니다.

이 패턴은 공통 인터페이스를 사용하므로 데코레이션된 객체를 원본 객체와 동일하게 취급할 수 있습니다.

기본 구조

컴포넌트 인터페이스를 정의합니다:

interface Coffee {
    String getDescription();
    double getCost();
}

인터페이스를 구현하는 구체적인 컴포넌트를 생성합니다:

class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

동일한 인터페이스를 구현하고 감싸진 객체에 대한 참조를 유지하는 추상 데코레이터 클래스를 생성합니다:

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

추상 데코레이터를 확장하는 구체적인 데코레이터를 생성합니다:

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

데코레이터 사용하기

여러 데코레이터를 쌓아서 기능을 결합합니다:

Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);

System.out.println(order.getDescription());  // Simple Coffee, Milk, Sugar
System.out.println(order.getCost());         // 2.75

데코레이터는 자신의 동작을 추가하면서 감싸진 객체로 호출을 위임합니다. 각 데코레이터는 독립적이며 자유롭게 조합할 수 있습니다.

직접 해보기

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 전송할 메시지 읽기
        String message = scanner.nextLine();
        
        // 쉼표로 구분된 채널 목록 읽기 (예: "email,sms" 또는 "push,email,sms")
        String channelsInput = scanner.nextLine();
        
        // TODO: BasicNotifier로 시작하기
        
        // TODO: 채널 입력을 쉼표를 기준으로 분리하기
        
        // TODO: 각 채널을 순회하며 적절한 데코레이터로 notifier를 감싸기
        // 유효한 채널은 "email", "sms", "push"입니다
        
        // TODO: 메시지와 함께 완전히 데코레이션된 notifier의 send 호출하기
        
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨