Menu
CoddyTech

策略模式

本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 49 课,共 65 课。

策略模式将算法的每个版本放在独立的对象中,这样你就可以在程序运行时切换算法。使用它的对象,即上下文,只调用一个方法,不关心它持有的是哪个版本。

下面是两种攻击策略,一个父类和一个子类,它们有相同的方法 damage():

# attack_style.gd
class_name AttackStyle
extends RefCounted

func damage(power: int) -> int:
	return power
# heavy_attack.gd
class_name HeavyAttack
extends AttackStyle

func damage(power: int) -> int:
	return power * 2

上下文会存储一种策略,并将计算交给它:

# fighter.gd
class_name Fighter
extends RefCounted

var power := 10
var style: AttackStyle = AttackStyle.new()

func set_style(new_style: AttackStyle) -> void:
	style = new_style

func attack() -> void:
	print("Hit for ", style.damage(power))
# main.gd
var fighter := Fighter.new()
fighter.attack()
fighter.set_style(HeavyAttack.new())
fighter.attack()
Hit for 10
Hit for 20

Fighter 从不检查自己持有哪种风格。每种新风格都是一个新类,而 fighter.gd 保持不变;在 attack() 中添加 if style == "heavy" 条件链,会随着每种风格的增加而变长。

如果策略只是一个不包含自身数据的小函数,那么使用 Callable 就足够了。上下文会保存这个函数,并通过 call() 运行它:

# price_calculator.gd
class_name PriceCalculator
extends RefCounted

var discount: Callable = func(price): return price

func final_price(price: int) -> int:
	return discount.call(price)
# main.gd
var calc := PriceCalculator.new()
print(calc.final_price(80))
calc.discount = func(price): return price / 2
print(calc.final_price(80))
calc.discount = func(price): return max(price - 25, 0)
print(calc.final_price(80))
80
40
55

当一种策略有自己的设置或包含多个方法时,使用类;进行一次简短计算时,使用可调用对象。你以前用过这个思路:传给 sort_custom() 的比较函数就是一种排序策略。

challenge icon

挑战

中等

购物车可以在结账时切换折扣策略。discount.gd 中的 Discount 是基础策略:不打折。再编写两个策略和购物车:

  • PercentOff(用百分比创建):apply(total) 返回 total - total * percent / 100(整数运算),对于 10% 的折扣,label() 返回 10% off。
  • FixedOff(用金额创建):apply(total) 返回总金额减去折扣金额,但结果不能低于 0;折扣金额为 30 时,label() 返回 30 off。
  • Cart:set_discount(new_discount) 保存策略,checkout() 汇总价格并返回 Subtotal TOTAL, LABEL, pay PRICE,其中 PRICE 是策略的 apply() 返回的值。

提供的代码会读取一行价格、后续结账次数,以及每行一个策略:percent N、fixed N 或 none。价格为 50 30 40,策略依次为 percent 10、fixed 30、none 时,输出为:

Subtotal 120, 10% off, pay 108
Subtotal 120, 30 off, pay 90
Subtotal 120, no discount, pay 120

你需要在 cart.gd、percent_off.gd 和 fixed_off.gd 中编写代码。main.gd 中包含提供的输入和输出代码,不能编辑。

亲手试试

extends Node

func _ready():
	# 提供的输入输出代码:保持原样。
	var cart := Cart.new()
	for price in OS.read_string_from_stdin().split(" "):
		cart.add_item(int(price))
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		if parts[0] == "percent":
			cart.set_discount(PercentOff.new(int(parts[1])))
		elif parts[0] == "fixed":
			cart.set_discount(FixedOff.new(int(parts[1])))
		else:
			cart.set_discount(Discount.new())
		print(cart.checkout())
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: GDScript 在线编译器