装饰器模式
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 93 课,共 107 课。
装饰器模式允许你通过用其他对象包装对象,动态地为对象添加新行为。适配器会更改接口以匹配另一个接口,而装饰器保留相同的接口,但通过在原始对象外层逐层包装来增强功能。
在 Go 中,我们通过让基础类型和装饰器都实现同一个接口来实现这一点:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}装饰器会包装另一个 Notifier,并在委托给它之前或之后添加行为:
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}装饰器的强大之处在于可以叠加多个包装器来组合不同的行为:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Sending: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLO当你需要在不修改对象代码的情况下为对象添加职责时,装饰器模式非常合适,尤其是在需要组合不同功能时。它常用于日志记录、缓存、身份验证和压缩层。
挑战
简单让我们使用装饰器模式构建一个咖啡店点单系统!你将创建一种基础咖啡饮品,以及可以添加牛奶、糖和鲜奶油等配料的装饰器。每个装饰器都会包装前一个饮品,逐步完善描述和价格,这是装饰器层层叠加功能的绝佳现实示例。
你需要将代码组织在三个文件中:
beverage.go:定义核心接口和基础咖啡类型。创建一个包含两个方法的
Beverage接口:Description() string:返回饮品的名称Cost() float64:返回价格
将
Coffee结构体实现为基础饮品。它的描述应为Coffee,价格应为2.00。decorators.go:创建可以为任意饮品添加配料的装饰器类型。每个装饰器都会包装一个
Beverage,并实现相同的接口。创建三个装饰器:MilkDecorator:在描述中添加, Milk,在价格上增加0.50SugarDecorator:在描述中添加, Sugar,在价格上增加0.25WhipDecorator:在描述中添加, Whip,在价格上增加0.75
每个装饰器都应调用被包装饮品的方法,并增强返回结果。
main.go:通过叠加装饰器来制作定制饮品。读取要添加的配料数量。然后逐个读取配料类型(
milk、sugar或whip),并使用相应的装饰器包装当前饮品。添加完所有配料后,分两行输出最终饮品的描述和价格。价格格式为
$X.XX,并且必须恰好保留两位小数。
输入格式如下:
- 第 1 行:要添加的配料数量
- 后续各行:每行一种配料类型(
milk、sugar或whip)
例如,给定:
2
milk
sugar输出应为:
Coffee, Milk, Sugar
$2.75再例如,给定:
3
whip
milk
milk输出应为:
Coffee, Whip, Milk, Milk
$3.75再例如,给定:
0输出应为:
Coffee
$2.00注意,每个装饰器都会包装前一个饮品,逐步累加描述和价格。你可以多次添加同一种配料(例如双份牛奶),装饰器的顺序也会决定描述中的顺序。基础咖啡不需要了解任何配料,每个装饰器只需增强它所包装的内容!
亲手试试
package main
import (
"fmt"
)
func main() {
// 读取配料的数量
var count int
fmt.Scanln(&count)
// 先创建一杯基础咖啡
var drink Beverage = &Coffee{}
// 读取每种配料,并用对应的装饰器包装饮品
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO:根据配料类型,用对应的装饰器包装 drink
// 为 "milk" 使用 MilkDecorator
// 为 "sugar" 使用 SugarDecorator
// 为 "whip" 使用 WhipDecorator
}
// TODO:输出最终的描述和价格
// 将价格格式化为 $X.XX,保留两位小数
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器