Menu
Coddy logo textTech

데코레이터 패턴

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

데코레이터 패턴(Decorator pattern)은 객체를 데코레이터라고 불리는 특별한 객체로 감싸서 객체에 동적으로 새로운 동작을 추가할 수 있게 해줍니다. 컴파일 타임에 동작을 추가하는 상속과 달리, 데코레이터는 원래 클래스를 수정하지 않고도 런타임에 추가적인 책임을 부여합니다.

핵심 아이디어는 원본 객체와 데코레이터 모두 동일한 인터페이스를 구현한다는 것입니다.

데코레이터는 원본 객체를 감싸고, 해당 객체에 작업을 위임하며, 실행 전후에 자체적인 동작을 추가합니다:

abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

class SugarDecorator implements Coffee {
  final Coffee _coffee;
  SugarDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Sugar';

  @override
  double getCost() => _coffee.getCost() + 0.2;
}

void main() {
  Coffee coffee = SimpleCoffee();
  coffee = MilkDecorator(coffee);
  coffee = SugarDecorator(coffee);

  print(coffee.getDescription());  // Simple Coffee, Milk, Sugar
  print(coffee.getCost());         // 2.7
}

각 데코레이터는 이전 객체를 감싸고 이를 확장합니다. 여러 데코레이터를 어떤 조합으로든 쌓을 수 있어, 가능한 모든 조합에 대해 서브클래스를 생성하지 않고도 유연한 동작을 만들 수 있습니다. 이 패턴은 동일한 클래스의 다른 객체에 영향을 주지 않으면서 객체에 기능을 추가해야 할 때 이상적입니다.

challenge icon

챌린지

쉬움

데코레이터(Decorator) 패턴을 사용하여 피자 주문 시스템을 만들어 봅시다! 각 토핑이 이전 항목을 감싸며 설명과 가격을 모두 추가하는 방식으로, 다양한 토핑으로 강화할 수 있는 기본 피자를 만들게 됩니다.

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

  • pizza.dart: 이 파일에는 피자 인터페이스와 모든 데코레이터 클래스가 포함됩니다. 먼저 String getDescription()double getPrice()라는 두 개의 메서드를 정의하는 추상 클래스 Pizza를 만드세요. 그런 다음 Pizza를 구현하는 PlainPizza 클래스를 만듭니다. 이 클래스는 설명으로 Plain Pizza를 반환하고 가격은 8.0입니다. 이제 각각 Pizza를 감싸고 기능을 강화하는 세 개의 데코레이터 클래스를 만드세요:
    • CheeseDecorator - 설명에 , Cheese를 추가하고 가격에 1.5를 추가합니다.
    • PepperoniDecorator - 설명에 , Pepperoni를 추가하고 가격에 2.0를 추가합니다.
    • MushroomDecorator - 설명에 , Mushrooms를 추가하고 가격에 1.25를 추가합니다.
    각 데코레이터는 Pizza를 구현해야 하며, 생성자에서 Pizza 객체를 받아야 하고, 자신의 기여분을 더하기 전에 감싸진 피자 객체에 작업을 위임해야 합니다.
  • main.dart: 피자 파일을 임포트하고 데코레이터가 어떻게 쌓여 맞춤형 주문을 구성하는지 보여주세요. PlainPizza로 시작하여 CheeseDecorator로 감싸고, 다시 PepperoniDecorator로 감싸고, 마지막으로 모든 것을 MushroomDecorator로 감싸세요. 최종 피자의 설명을 출력한 다음, Total: $와 함께 가격을 출력하세요.

각 데코레이터가 이전 데코레이터 위에 어떻게 쌓이는지 확인해 보세요. 단순하고 단일 책임을 가진 데코레이터들을 쌓아 올림으로써 모든 토핑이 추가된 피자를 만들고 있습니다!

예상 출력:

Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75

치트 시트

데코레이터 패턴(Decorator pattern)은 객체를 데코레이터 객체로 감싸서 동적으로 새로운 동작을 추가합니다. 원래 객체와 데코레이터 모두 동일한 인터페이스를 구현합니다.

주요 구성 요소:

  • 공통 메서드를 정의하는 추상 클래스 또는 인터페이스
  • 기본 기능을 구현하는 구체 클래스
  • 원래 객체를 감싸고 기능을 확장하는 데코레이터 클래스들

각 데코레이터는 다음을 수행합니다:

  • 장식하는 객체와 동일한 인터페이스를 구현합니다
  • 감싸진 객체에 대한 참조를 유지합니다
  • 호출을 감싸진 객체로 위임합니다
  • 위임 전후에 자체적인 동작을 추가합니다
abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

// 여러 데코레이터를 중첩해서 사용
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);

데코레이터는 어떤 조합으로든 쌓을 수 있어, 가능한 모든 조합에 대해 서브클래스를 만들지 않고도 유연한 동작을 생성할 수 있습니다.

직접 해보기

import 'pizza.dart';

void main() {
  // TODO: PlainPizza 생성
  
  // TODO: CheeseDecorator로 감싸기
  
  // TODO: PepperoniDecorator로 감싸기
  
  // TODO: MushroomDecorator로 모두 감싸기
  
  // TODO: 최종 피자의 설명(description) 출력
  
  // TODO: "Total: $" 뒤에 가격을 붙여서 출력
}
quiz icon실력 점검

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

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