Mẫu Observer
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 48 trên 65.
Mẫu Observer cho phép một đối tượng, gọi là chủ thể, thông báo cho nhiều đối tượng khác, gọi là các đối tượng quan sát, rằng có điều gì đó đã xảy ra. Chủ thể không biết các đối tượng quan sát sẽ làm gì với thông tin đó; nó chỉ thông báo.
Viết thủ công, đối tượng giữ một mảng các bên theo dõi và gọi cùng một phương thức trên từng bên:
# health.gd
class_name Health
extends RefCounted
var observers: Array = []
var hp := 100
func add_observer(observer) -> void:
observers.append(observer)
func take_damage(amount: int) -> void:
hp -= amount
for observer in observers:
observer.on_hp_changed(hp)Godot tích hợp sẵn mẫu này. Tín hiệu là thông báo của đối tượng phát đi, còn mỗi lần gọi connect() sẽ thêm một đối tượng quan sát, nên bạn không cần mảng và vòng lặp nữa:
# health.gd
class_name Health
extends RefCounted
signal hp_changed(new_hp: int)
var hp := 100
func take_damage(amount: int) -> void:
hp -= amount
hp_changed.emit(hp)Một đối tượng lắng nghe là bất kỳ đối tượng nào có phương thức với các tham số khớp với tín hiệu:
# health_bar.gd
class_name HealthBar
extends RefCounted
func on_hp_changed(new_hp: int) -> void:
print("Bar: ", new_hp, "/100")# damage_log.gd
class_name DamageLog
extends RefCounted
func on_hp_changed(new_hp: int) -> void:
if new_hp < 50:
print("Log: low health!")# main.gd
var health := Health.new()
var bar := HealthBar.new()
var damage_log := DamageLog.new()
health.hp_changed.connect(bar.on_hp_changed)
health.hp_changed.connect(damage_log.on_hp_changed)
health.take_damage(70)Bar: 30/100
Log: low health!Health không biết thanh trạng thái hay nhật ký có tồn tại. Hiệu ứng âm thanh hoặc thành tích cũng có thể lắng nghe mà không cần thay đổi health.gd.
Có ba quy tắc giúp các observer hoạt động ổn định:
- Kết nối cùng một phương thức hai lần sẽ gây ra lỗi. Nếu có thể xảy ra tình huống này, hãy kiểm tra trước bằng
hp_changed.is_connected(bar.on_hp_changed).disconnect()xóa một observer, và ngắt kết nối một observer chưa được kết nối cũng gây ra lỗi. - Kết nối không giữ cho observer tồn tại. Hãy giữ observer trong một biến hoặc một mảng: đối tượng được tạo bên trong lệnh gọi
connect()sẽ bị giải phóng ngay lập tức và không bao giờ nhận được tín hiệu. - Hãy kết nối các phương thức của observer. Một lambda sử dụng đối tượng phát tín hiệu và được kết nối với chính tín hiệu của đối tượng đó sẽ khiến đối tượng phát tín hiệu tồn tại mãi mãi. Godot sẽ báo đây là một rò rỉ khi trò chơi đóng.
Thử thách
Trung bìnhXây dựng một trạm thời tiết dùng tín hiệu để thông báo cho các màn hình.
Trong weather_station.gd:
- Khai báo tín hiệu
temperature_changed(value: float). attach(display)kết nối phương thứcon_temperature_changedcủa màn hình. Gắn một màn hình đã được gắn rồi sẽ không làm gì.detach(display)ngắt kết nối màn hình đó. Gỡ một màn hình chưa được gắn sẽ không làm gì.set_temperature(value)lưu giá trị vàotemperaturevà phát tín hiệu kèm theo giá trị đó.
Trong weather_display.gd, on_temperature_changed(value) in ra tên màn hình và giá trị với một chữ số thập phân, như Phone: 21.5 C.
Code được cung cấp đọc tên các màn hình trên một dòng, số lượng lệnh tiếp theo, rồi đến mỗi lệnh trên một dòng: attach NAME, detach NAME hoặc temp VALUE. Cuối cùng, chương trình in ra nhiệt độ cuối cùng. Với các tên Phone Tablet và các lệnh attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19, đầu ra là:
Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0Code của bạn nằm trong weather_station.gd và weather_display.gd. main.gd chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.
Tự làm thử
extends Node
func _ready():
# Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
var station := WeatherStation.new()
var displays := {}
for display_name in OS.read_string_from_stdin().split(" "):
displays[display_name] = WeatherDisplay.new(display_name)
var count := int(OS.read_string_from_stdin())
for i in count:
var parts := OS.read_string_from_stdin().split(" ")
match parts[0]:
"attach":
station.attach(displays[parts[1]])
"detach":
station.detach(displays[parts[1]])
"temp":
station.set_temperature(float(parts[1]))
print("Final temperature: %.1f" % station.temperature)
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
1Nền tảng về OOP
Tệp bên ngoàiGiới thiệu về OOPLớp và đối tượngTừ khóa selfPhương thứcThuộc tínhHàm khởi tạo (_init)Ôn tập: máy tính đơn giản4Tính đa hình
Xem lại cách ghi đè phương thứcĐịnh kiểu vịtLớp và phương thức trừu tượngThiết kế interfaceÔn tập: máy tính hình học7Tín hiệu
Tín hiệu là gì?Khai báo và phát tín hiệuKết nối với tín hiệuTín hiệu có đối sốÔn tập: hệ thống sức khỏe10Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổng hợp2Thuộc tính của lớp
Biến instance và biến staticHàm staticPhương thức thiết lập thuộc tínhPhương thức lấy thuộc tínhQuy ước về tính riêng tưÔn tập: trình quản lý tài khoản ngân hàng5Tính đóng gói
Thành viên công khai và riêng tưXác thực bằng phương thức setterẨn thông tinThuộc tính chỉ đọcÔn tập: hệ thống hồ sơ học sinh8Khái niệm OOP nâng cao
Kết hợp và kế thừaLớp bên trongNode dưới dạng thành phầnRefCounted và NodeEnum trong lớpTự luyện tập: Trình biên dịch GDScript online