데코레이터 패턴
Coddy Swift 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 47번째.
데코레이터 패턴은 동일한 프로토콜을 따르는 다른 객체로 객체를 감싸 객체에 동작을 추가합니다. 래퍼는 호출을 감싸진 객체에 전달하고, 전이나 후에 자신의 동작을 추가합니다:
protocol Coffee {
func cost() -> Int
func label() -> String
}
struct Espresso: Coffee {
func cost() -> Int { 200 }
func label() -> String { "espresso" }
}
struct WithMilk: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 50 }
func label() -> String { base.label() + " + milk" }
}
let drink = WithMilk(base: Espresso())
print(drink.label())
print(drink.cost())출력:
espresso + milk
250데코레이터 자체가 Coffee이므로, 모든 조합마다 서브클래스를 만들지 않아도 데코레이터를 어떤 조합과 순서로든 서로 감쌀 수 있습니다:
protocol Coffee { func cost() -> Int; func label() -> String }
struct Espresso: Coffee { func cost() -> Int { 200 }; func label() -> String { "espresso" } }
struct WithMilk: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 50 }
func label() -> String { base.label() + " + milk" }
}
struct WithSyrup: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 30 }
func label() -> String { base.label() + " + syrup" }
}
let fancy = WithSyrup(base: WithMilk(base: WithMilk(base: Espresso())))
print(fancy.label())
print(fancy.cost())출력:
espresso + milk + milk + syrup
330데코레이터는 변경되지 않은 기존 컴포넌트 주변에 로깅, 캐싱 또는 유효성 검사와 같은 횡단 관심사 추가 기능을 적용합니다:
protocol DataSource { func fetch(_ id: Int) -> String }
struct Database: DataSource { func fetch(_ id: Int) -> String { "row \(id)" } }
struct Logged: DataSource {
let inner: any DataSource
func fetch(_ id: Int) -> String {
print("fetching \(id)")
return inner.fetch(id)
}
}
let source: any DataSource = Logged(inner: Database())
print(source.fetch(7))출력:
fetching 7
row 7객체를 사용하는 코드는 프로토콜만 보므로, 장식된 객체와 일반 객체를 구분할 수 없습니다. 이것이 나중에 이 패턴을 안전하게 추가할 수 있는 이유입니다.
func printReceipt(_ c: any Coffee) { // Espresso와 모든 래핑된 버전에 대해 작동합니다
print("\(c.label()): \(c.cost())")
}챌린지
쉬움피자는 데코레이터로 구성됩니다. Pizza 프로토콜에는 price()와 toppings()(이름 배열)가 있습니다. Base의 가격은 800이며 토핑은 없습니다. Cheese(+150), Olives(+100), Double(내부의 모든 항목 가격을 2배로 만들고 토핑을 두 번 반복함) 데코레이터를 구현하세요. Base에서 시작하여 이름 순서대로 각 이름에 해당하는 데코레이터 하나씩으로 감싸는 build(_:)를 완성하세요.
제공된 코드는 쉼표로 구분된 토핑 이름이 포함된 줄(또는 빈 줄)을 읽고, 각 피자를 구성한 다음 1050: cheese, olives 또는 800: plain을 출력합니다.
코드는 Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift 및 Functions.swift에 작성하세요. main.swift에는 제공된 입력/출력 코드가 포함되어 있으며 편집할 수 없습니다.
직접 해보기
// 제공된 입출력 코드: 있는 그대로 유지하세요
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
let names = line.isEmpty ? [] : line.split(separator: ",").map(String.init)
let p = build(names)
let tops = p.toppings()
print("\(p.price()): " + (tops.isEmpty ? "plain" : tops.joined(separator: ", ")))
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: Swift 플레이그라운드