Menu
CoddyTech

装饰器模式

本课属于 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 等等。有了装饰器,两个小类就能覆盖所有组合,而新增一种附魔只需要再添加一个文件。

challenge icon

挑战

中等

一家咖啡店使用装饰器制作饮品。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()])
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: GDScript 在线编译器