策略模式
本课属于 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 20Fighter 从不检查自己持有哪种风格。每种新风格都是一个新类,而 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() 的比较函数就是一种排序策略。
挑战
中等购物车可以在结账时切换折扣策略。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())
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: GDScript 在线编译器