Menu
CoddyTech

Mẫu Strategy

Thuộc phần Lập trình hướng đối tượng trong hành trình GDScript của Coddy. Bài học 49 trên 65.

Mẫu Strategy đặt mỗi phiên bản của một thuật toán trong một đối tượng riêng, nhờ đó bạn có thể thay đổi thuật toán khi chương trình đang chạy. Đối tượng sử dụng thuật toán đó, gọi là ngữ cảnh, gọi một phương thức và không cần quan tâm nó đang giữ phiên bản nào.

Đây là hai chiến lược tấn công, một lớp cha và một lớp con có cùng phương thức 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

Ngữ cảnh lưu một chiến lược và giao việc tính toán cho chiến lược đó:

# 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 không bao giờ kiểm tra mình đang giữ kiểu nào. Mỗi kiểu mới là một lớp mới, còn fighter.gd vẫn giữ nguyên; một chuỗi if style == "heavy" bên trong attack() sẽ dài thêm với mỗi kiểu mới.

Khi một chiến lược chỉ là một hàm nhỏ không có dữ liệu riêng, một Callable là đủ. Ngữ cảnh lưu hàm và chạy hàm bằng 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

Dùng lớp (class) khi một chiến lược có cài đặt riêng hoặc nhiều phương thức, và dùng Callable cho một phép tính ngắn. Bạn đã từng dùng ý tưởng này: hàm so sánh mà bạn truyền vào sort_custom() chính là một chiến lược sắp xếp.

challenge icon

Thử thách

Trung bình

Giỏ hàng của cửa hàng có thể đổi chiến lược giảm giá khi thanh toán. Discount trong discount.gd là chiến lược cơ sở, không giảm giá. Hãy viết thêm hai chiến lược và giỏ hàng:

  • PercentOff (được tạo với một tỷ lệ phần trăm): apply(total) trả về total - total * percent / 100 (phép toán số nguyên), còn label() trả về 10% off khi tỷ lệ là 10 phần trăm.
  • FixedOff (được tạo với một số tiền): apply(total) trả về tổng trừ đi số tiền đó, nhưng không bao giờ thấp hơn 0, còn label() trả về 30 off khi số tiền là 30.
  • Cart: set_discount(new_discount) lưu chiến lược, còn checkout() cộng các mức giá và trả về Subtotal TOTAL, LABEL, pay PRICE, trong đó PRICE là giá trị mà apply() của chiến lược trả về.

Code được cung cấp đọc giá trên một dòng, số lượt thanh toán tiếp theo, rồi đọc một chiến lược trên mỗi dòng: percent N, fixed N hoặc none. Với các mức giá 50 30 40 và các chiến lược percent 10, fixed 30, none, đầu ra là:

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

Code của bạn nằm trong cart.gd, percent_off.gd và fixed_off.gd. main.gd chứa code đầu vào/đầu ra được cung cấp và không thể chỉnh sửa.

Tự làm thử

extends Node

func _ready():
	# Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
	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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch GDScript online