装饰器模式
本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 52 课,共 65 课。
装饰器模式通过将对象包装在另一个具有相同方法的对象中,为其添加行为。每个包装器都会调用其中的对象,并添加自己的部分,包装器可以按任意顺序嵌套。
# weapon.gd
class_name Weapon
extends RefCounted
func get_damage() -> int:
return 10
func describe() -> String:
return "Sword"装饰器扩展了 Weapon,因此在需要武器的任何地方都可以使用它,而且它会持有自己包装的武器:
# fire_enchant.gd
class_name FireEnchant
extends Weapon
var inner: Weapon
func _init(wrapped: Weapon) -> void:
inner = wrapped
func get_damage() -> int:
return inner.get_damage() + 5
func describe() -> String:
return inner.describe() + " + Fire"SharpEnchant 的写法相同,但它的 get_damage() 会返回 inner.get_damage() * 2,而它的 describe() 会添加 " + Sharp"。
# main.gd
var weapon: Weapon = Weapon.new()
print(weapon.describe(), ": ", weapon.get_damage())
weapon = FireEnchant.new(weapon)
weapon = SharpEnchant.new(weapon)
print(weapon.describe(), ": ", weapon.get_damage())Sword: 10
Sword + Fire + Sharp: 30调用会逐层向内传递:SharpEnchant 调用 FireEnchant,后者再调用剑。剑返回 10,火焰将其变为 15,锋利效果再将其翻倍,得到 30。按另一种顺序调用时,FireEnchant.new(SharpEnchant.new(Weapon.new())),伤害为 10 * 2 + 5,也就是 25。
如果没有装饰器,每种组合都需要一个单独的子类:FireSword、SharpSword、FireSharpSword 等等。有了装饰器,两个小类就能覆盖所有组合,而新增一种附魔只需要再添加一个文件。
挑战
中等一家咖啡店使用装饰器制作饮品。Beverage(beverage.gd)声明了 cost() 和 description();Espresso(espresso.gd,$3)和 Tea(tea.gd,$2)已经编写完成。每个装饰器都会将它包装的饮品存储在 inner 中(构造函数已经编写完成)。在以下类中重写 cost() 和 description():
Milk:价格增加1,并在描述中添加+ Milk。Mocha:价格增加2,并添加+ Mocha。Large:将内部所有内容的价格加倍,并在描述前添加Large。
给定的代码会读取后续订单的数量,然后每行读取一个订单:饮品(espresso 或 tea)及其配料,配料按从左到右的顺序添加。对于 espresso milk large 和 tea mocha milk,输出为:
Large Espresso + Milk: $8
Tea + Mocha + Milk: $5你的代码应写在 milk.gd、mocha.gd 和 large.gd 中。main.gd 包含给定的输入/输出代码,不能编辑。
亲手试试
extends Node
func _ready():
# 已提供的输入输出代码:保持原样
var count := int(OS.read_string_from_stdin())
for i in count:
var words := OS.read_string_from_stdin().split(" ")
var drink: Beverage = Tea.new()
if words[0] == "espresso":
drink = Espresso.new()
for extra in words.slice(1):
match extra:
"milk":
drink = Milk.new(drink)
"mocha":
drink = Mocha.new(drink)
"large":
drink = Large.new(drink)
print("%s: $%d" % [drink.description(), drink.cost()])
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: GDScript 在线编译器