Menu
CoddyTech

옵저버 패턴

Coddy GDScript 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 65개 중 48번째.

Observer 패턴을 사용하면 한 객체인 주체가 다른 여러 객체인 관찰자에게 어떤 일이 일어났다고 알릴 수 있습니다. 주체는 관찰자들이 그 소식으로 무엇을 하는지 알지 못하며, 그저 알리기만 합니다.

직접 작성하면 subject가 observer 배열을 유지하고 각 observer에서 동일한 메서드를 호출합니다:

# 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에는 이 패턴이 기본으로 제공됩니다. 시그널은 주체의 알림이며, connect()를 호출할 때마다 관찰자가 추가되므로 배열과 반복문은 더 이상 필요하지 않습니다.

# 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)

옵저버는 매개변수가 시그널과 일치하는 메서드를 가진 객체입니다:

# 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는 막대나 로그가 존재하는지 전혀 모릅니다. 사운드 효과나 업적도 health.gd를 전혀 변경하지 않고 이벤트를 수신할 수 있습니다.

관찰자가 제대로 작동하도록 하는 세 가지 규칙:

  • 같은 메서드를 두 번 연결하면 오류가 발생합니다. 그런 일이 발생할 수 있다면 먼저 hp_changed.is_connected(bar.on_hp_changed)로 확인하세요. disconnect()는 관찰자를 제거하며, 연결되어 있지 않은 관찰자의 연결을 해제하는 것도 오류입니다.
  • 연결만으로는 관찰자가 계속 살아 있지 않습니다. 관찰자를 변수나 배열에 보관하세요. connect() 호출 안에서 생성한 객체는 즉시 해제되어 신호를 받을 수 없습니다.
  • 관찰자의 메서드를 연결하세요. 주체를 사용하는 람다를 주체 자신의 신호에 연결하면 주체가 영원히 살아 있게 되며, Godot은 게임을 종료할 때 이를 누수로 보고합니다.
challenge icon

챌린지

중급

시그널로 디스플레이에 알림을 보내는 기상 관측소를 만드세요.

weather_station.gd에서:

  • 시그널 temperature_changed(value: float)을 선언하세요.
  • attach(display)는 디스플레이의 on_temperature_changed 메서드를 연결합니다. 이미 연결된 디스플레이를 연결해도 아무 작업도 하지 않습니다.
  • detach(display)는 연결을 해제합니다. 연결되지 않은 디스플레이의 연결을 해제해도 아무 작업도 하지 않습니다.
  • set_temperature(value)는 값을 temperature에 저장하고 해당 값과 함께 시그널을 발생시킵니다.

weather_display.gd에서 on_temperature_changed(value)는 디스플레이 이름과 값을 소수점 한 자리까지 출력합니다. 예: Phone: 21.5 C.

제공된 코드는 한 줄에 디스플레이 이름을 읽고, 이어지는 명령의 개수를 읽은 다음, 한 줄에 하나씩 명령을 읽습니다: attach NAME, detach NAME 또는 temp VALUE. 마지막에는 최종 온도를 출력합니다. 이름이 Phone Tablet이고 명령이 attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19일 때 출력은 다음과 같습니다:

Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0

코드는 weather_station.gd와 weather_display.gd에 작성하세요. main.gd에는 제공된 입출력 코드가 있으며 수정할 수 없습니다.

직접 해보기

extends Node

func _ready():
	# 제공된 입출력 코드: 그대로 유지하세요
	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)
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 GDScript 컴파일러