데코레이터 패턴
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
}각 데코레이터는 이전 객체를 감싸고 이를 확장합니다. 여러 데코레이터를 어떤 조합으로든 쌓을 수 있어, 가능한 모든 조합에 대해 서브클래스를 생성하지 않고도 유연한 동작을 만들 수 있습니다. 이 패턴은 동일한 클래스의 다른 객체에 영향을 주지 않으면서 객체에 기능을 추가해야 할 때 이상적입니다.
챌린지
쉬움데코레이터(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: $" 뒤에 가격을 붙여서 출력
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.