이 글은 해당 유튜브를 보고 진행한 것에 대해 공부한 것을 정리한 글입니다.
관찰자 패턴 (Observer Pattern)

Observer 패턴은 라디오 송신탑(Subject)과 청취자(Observer)의 관계에 비유할 수 있습니다.
즉, 한 오브젝트(Subject)의 상태가 바뀌면, 이에 종속된 모든 Observer에게 자동으로 알림이 전달되는 구조입니다.
핵심 특징
- 한 오브젝트의 상태가 변경되면 종속된 모든 오브젝트에 자동으로 알립니다.
- '1 대 다' 종속 관계를 통해 오브젝트가 통신하되, 느슨한 결합을 유지합니다.
- Observer는 Subject에 대해 종속 관계지만, 서로에 대해서는 알지 못합니다.
- Subject는 신호를 수신한 Observer가 무엇을 하든 관여하지 않습니다.
장점
느슨한 결합
- 낮은 결합도 유지로 유연한 설계가 가능합니다.
재사용성
- 주체와 옵저버 모두 결합도가 낮기 때문에, 재사용성이 뛰어납니다.
확장성
- 주체 클래스나 기존의 옵저버를 수정할 필요 없이, 새로운 옵저버를 쉽게 추가할 수 있습니다.
단일 책임 원칙 준수
- 단일 책임 원칙을 따르며, 역할이 분리됩니다.
- 주체는 상태 변화를 관리하는 역할만, 옵저버는 그 변화를 처리하는 역할에만 집중합니다.
실시간 동적 업데이트
- 런타임에 옵저버를 등록 / 해제가 용이합니다.
손쉬운 구현
- C# 및 유니티에서 쉽게 구현이 가능합니다.
- C#에는 이미 이벤트 시스템이 확립되어 있고, 자체 델리게이트를 정의하는 대신 System.Action 델리게이트를 사용할 수 있습니다. Unity에서는 UnityEvent 및 UnityAction을 사용할 수 있습니다.
이벤트 기반 시스템 구현 용이
- UI 이벤트(MVP/MVC), 네트워크 이벤트, 데이터 변경 이벤트 처리 등의 이벤트 기반 시스템 구현이 용이합니다.
- 특히 사용자 인터페이스(MVP/MVC 패턴)에 매주 적합합니다. 코어 게임 플레이 코드를 UI 로직과 별도로 둘 수 있습니다.
※ MVP 패턴, MVC 패턴
- MVC (Model - View - Controller)
- Model : 데이터 및 비즈니스 로직
- View : 사용자 인터페이스
- Controller : 사용자의 입력 처리 및 Model - View 연결
- 예시
- 사용자가 버튼 클릭 → Controller가 처리 → Model 업데이트 → View 반영
- MVP (Model - View - Presenter)
- Model : 데이터
- View : UI
- Presenter : View의 이벤트 처리, Model 제어, View 갱신
- View는 Presenter에 모든 것을 위임하고, Presenter가 로직을 갖습니다.
단점
복잡도 증가
- 다수의 옵저버가 존재할 때, 시스템의 복잡도가 증가하여 시스템 추적이 어려워집니다.
호출 순서 예측 불가
- 이벤트가 발생해도 핸들러 호출 순서는 보장되지 않습니다.
메모리 누수
- 옵저버가 주체의 이벤트를 구독한 후 해제하지 않으면, GC 대상이 되지 않아 메모리 누수가 발생할 수 있습니다.
성능 저하
- 이벤트 기반 아키텍처는 더 많은 오버헤드를 유발합니다.
- 각 핸들러 호출은 동적으로 연결된 메서드(Delegate) 실행이기 때문에, 직접 호출보다 비용이 큽니다.
- 옵저버의 수가 많거나 각 옵저버가 처리해야 할 로직이 복잡하면 성능이 저하될 수 있습니다.
순환 참조
- 주체와 옵저버 간에 순환 참조(Circular Reference)가 발생할 수 있습니다.
강한 결합
- 주체와 옵저버 간의 강한 결합이 발생할 수 있습니다. 따라서 남용하면 시스템의 모듈화가 어려워질 수 있습니다.
- Observer가 Subject의 내부 상태나 메서드를 직접 사용하는 경우, 구독자가 이벤트를 구독하는 방식이 직접 연결되는 경우 강한 결합이 발생할 수 있습니다.
관찰자에게 이벤트를 정의하는 클래스에 대한 레퍼런스가 필요
// observer가 publisher를 알아야 구독 가능
publisher.OnChanged += observer.Handler;
- 관찰자가 이벤트를 구독하려면, 그 이벤트가 어느 객체에 정의되어 있는지를 반드시 알아야 합니다.
- 즉, 구독을 위해서는 이벤트를 발생하는 객체(Subject)의 인스턴스 또는 정적 참조가 필요합니다.
- 이 때문에 완전히 독립적인 설계는 어렵고, 일정 수준의 종속성은 존재하게 됩니다.
- 모든 이벤트를 처리하는 정적 EventManager를 사용하면 서로 얽힌 오브젝트를 더 쉽게 정리할 수 있습니다.
디버깅 어려움
- 이벤트가 객체에 의해 처리되기 때문에 호출 스택이 명확하지 않고, 이벤트 흐름이 간접적이어서 추적이 어렵습니다.
- 이벤트 호출이 암묵적으로 발생하기 때문에 어느 시점에 어떤 이벤트가 호출됐는지 로그 없이는 추적하는 것이 불가능합니다.
- 즉, 이벤트는 '자동 호출되는 콜백'이므로 일반적인 메서드 호출처럼 직관적인 흐름 추적이 어렵습니다.
개선 방안
다음은 메모리 누수 방지, 디버깅 용이, 이벤트 남용 방지, 성능 최적화를 위한 개선 방안입니다.
ObservableCollection 클래스 사용
- C#에는 특정 변경 사항을 추적하기 위한 동적 ObservableCollection을 제공합니다.
- ObservableCollection 클래스는 컬렉션 변화 감지가 가능합니다.
- 이를 이용해 항목이 추가, 제거되거나 목록이 새로 고침되면 관찰자에게 알릴 수 있습니다.
고유 인스턴스 ID를 인수로 전달
- 계층 구조의 각 게임 오브젝트에는 고유의 인스턴스 ID가 있습니다.
- 둘 이상의 관찰자에게 적용할 수 있는 이벤트를 트리거하는 경우, 고유 ID 이벤트로 전달할 수 있습니다.(Action<int> 사용)
- 게임 오브젝트가 고유 ID와 일치하면 이벤트 핸들러의 로직만 실행하면 됩니다.
정적 EventManager 설계
- 정적 클래스 또는 싱글톤을 통해 중앙 집중식 이벤트 관리가 가능합니다.
이벤트 대기열 설계
- 씬에 오브젝트가 많은 경우에는 이벤트를 한꺼번에 발생시키지 않는 편이 좋습니다.
- 관찰자 패턴과 커맨드 패턴을 조합해 이벤트를 이벤트 대기열 캡슐화할 수 있습니다.
- 그런 다음 커맨드 버퍼를 사용해 이벤트를 한 번에 하나씩 재생할 수 있으며, 필요한 경우 선택적으로 무시할 수도 있습니다.
사용 예시
- 이벤트 기반 프로그래밍
- MVC / MVP 디자인 패턴
- 게임 내 상태 전파, UI 반응 처리 등
C#의 Delegate와 Event
Delegate
public delegate void MyDelegate(string message);
public class Broadcaster {
public MyDelegate OnBroadcast;
}
Delegate는 메서드를 참조할 수 있는 형식입니다. 쉽게 말하면 함수를 변수처럼 저장하고 실행할 수 있는 기능입니다.
Delegate는 이미 옵저버 패턴이 사용되고 있습니다.
하지만 위의 코드와 같은 형태로만 사용하면 외부에서 자유롭게 할당 및 호출이 가능해 보안성이 떨어집니다.
Action
Action onClick;
Action<string> onMessage;
C#에서는 void를 반환하고 매개변수를 받을 수 있는 대표적인 델리게이트 형식으로 Action을 제공합니다.
Event
public class Broadcaster {
public event Action onBroadcast;
public void Broadcast() {
onBroadcast?.Invoke(); // 안전한 호출
}
}
event는 delegate를 기반으로 하여 이벤트를 정의합니다.
이를 통해 외부에서 직접 이벤트를 호출하지 못하게 보호할 수 있습니다.
event를 사용하면, 이벤트를 구독하는 것(AddListener)은 가능하지만, 직접 호출(Invoke)은 불가능합니다.
즉, event는 Publisher(주체)만 이벤트를 발생시킬 수 있고, Subscriber(구독자)는 구독(등록)만 가능하게 만들어, 캡슐화와 안정성을 보장합니다.
Action과 Event 사용 예시
using System;
// Subject
public class EventManager : MonoBehaviour
{
public static event Action OnClicked;
void OnGUI()
{
if(GUI.Button(new Rect(Screen.width / 2 - 50, 5, 100, 30),"Click"))
{
if(OnClicked != null)
OnClicked();
}
}
}
// Observer
public class TeleportScript : MonoBehaviour
{
void OnEnable()
{
EventManager.OnClicked += Teleport;
EventManager.OnClicked += PrintLogMessge;
}
void OnDisable()
{
EventManager.OnClicked -= Teleport;
EventManager.OnClicked -= PrintLogMessge;
}
void Teleport()
{
Vector3 pos = transform.position;
pos.y = Random.Range(1.0f, 3.0f);
transform.position = pos;
}
void PrintLogMessge()
{
Debug.Log("Teleport!");
}
}
C#에서의 Observer - 이벤트 시스템
이벤트 시스템 (Event System)

관찰자 패턴은 C# 언어에 빌트인되어 있을 정도로 매우 보편적으로 사용됩니다.
즉, 직접 Subject / Observer 클래스를 설계하지 않아도 이벤트 키워드로 간편하게 구현할 수 있습니다.
구조 구성 요소
퍼블리셔 (Publisher)
- 이벤트를 선언하고 발생(trigger) 시키는 주체입니다.
- event 키워드를 통해 Delegate를 기반으로 이벤트를 생성합니다.
구독자 (관찰자, Subscriber)
- 이벤트가 발생했을 때 반응하는 핸들러(메서드)를 정의합니다.
- 해당 이벤트 핸들러(메서드)는 델리게이트의 서명과 일치해야 합니다.
- 각 구독자(관찰자)의 이벤트 핸들러는 퍼블리셔의 이벤트를 구독합니다.
- 필요한 수만큼의 구독자가 구독에 참여하도록 할 수 있습니다.
- 구독에 참여하려는 관찰자는 모두 이벤트가 트리거되기를 기다립니다.
- += 연산자를 통해 이벤트에 구독합니다.
- −= 연산자를 통해 이벤트를 해제합니다.
트리거 (Trigger)
- 퍼블리셔가 이벤트 발생 시점에 모든 구독자의 핸들러를 호출할 때 사용됩니다.
- 비동기적이며, 호출 순서는 보장되지 않습니다.
이벤트 적용 예시
- 적 또는 플레이어 사망 이벤트
- 목표 달성 / 실패 처리
- 아이템 획득 알림
- UI 상태 변경 이벤트
- 데이터 값 변경 감지
기본 구조 코드 예시
public class Publisher {
public event Action OnDataChanged;
public void ChangeData() {
// 데이터 변경 로직
OnDataChanged?.Invoke(); // 이벤트 발생
}
}
public class Observer {
public void Subscribe(Publisher publisher) {
publisher.OnDataChanged += RespondToChange;
}
private void RespondToChange() {
Console.WriteLine("변경 감지됨!");
}
}
Event System 예시
아래의 관찰자 샘플 씬에서 ButtonSubject는 사용자가 마우스 버튼으로 Clicked 이벤트를 호출하도록 합니다.
그러면 AudioObserver, ParticleSystemObserver, AnimObserver 컴포넌트가 있는 여러 다른 게임 오브젝트가 고유의 방식으로 이벤트에 응답할 수 있습니다.

Subject : ButtonSubject.cs
public class ButtonSubject: MonoBehaviour
{
public event Action Clicked;
// ...
public void ClickButton()
{
Clicked ?. Invoke();
}
void Update()
{
CheckCollider();
}
private void CheckCollider()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitlnfo;
if (Physics.Raycast(ray, out hitlnfo, 100f))
{
if (hitInfo.collider == this.collider)
{
ClickButton();
}
}
}
}
}
Observer : AudioObserver.cs
public class AudioObserver : MonoBehaviour
{
// dependency to observe
[SerializeField] ButtonSubject subjectToObserve;
// ...
private void Awake()
{
if (subjectToObserve != null)
{
subjectToObserve.Clicked += OnThingHappened;
}
}
private void OnDestroy()
{
if (subjectToObserve != null)
{
subjectToObserve.Clicked -= OnThingHappened;
}
}
public void OnThingHappened()
{
StartCoroutine(PlayWithDelay());
}
IEnumerator PlayWithDelay()
{
yield return new WaitForSeconds(delay
audioSource.Stop();
audioSource.Play();
}
// ...
}
Unity의 UnityEvent와 UnityAction

Unity에서는 델리게이트와 이벤트를 더 직관적으로 사용할 수 있도록 자체 이벤트 시스템인 UnityAction과 UnityEvent를 제공합니다.
UnityAction
using UnityEngine.Events;
UnityAction myAction = MyMethod;
myEvent.AddListener(myAction);
- System.Action의 Unity 버전으로, Unity 전용 델리게이트(delegate) 타입입니다.
- using UnityEngine.Events; 를 통해 사용 가능합니다.
- 인스펙터에서는 설정할 수 없고, 코드로만 등록하는 것이 가능합니다.
- UnityEvent.AddListener(Events.UnityAction call);
UnityEvent
public UnityEvent onDeath;
void Die() {
onDeath.Invoke(); // 인스펙터에 연결된 함수들 실행
}
- 이벤트를 관리하기 위해 Unity에서 제공하는 이벤트 클래스입니다.
- Unity의 인스펙터 지원하여, 인스펙터를 통해 이벤트 연결이 가능합니다.
- 쉽게 설정하고 연결할 수 있는 이벤트 시스템으로 비프로그래머에게 유용합니다.
- 기술적 한계와 설계 구조로 인해 다소 제한적으로 사용할 수 있습니다.
- 제네릭 타입 지원이 제한적입니다.
- UnityEvent는 UnityEvent<int>, UnityEvent<string> 등 최대 4개의 매개변수까지만 지원합니다.
- 제네릭 구조가 복잡한 타입(List<T>, Dictionary<K, V> 등)은 인스펙터에서 사용할 수 없습니다.
- 복합 타입이나 커스텀 ㅡㅋㄹ래스는 인스펙터에서 드래그 앤 드롭으로 연결할 수 없고, 별도 코드 작성이 필요합니다.
- 런타임에만 동적 바인딩이 가능합니다.
- 인스펙터에서는 미리 연결해둘 수 있지만, 조건에 따라 동적으로 Listener를 바꾸거나 제거하는 등의 세밀한 제어는 코드로만 해야 합니다.
- 즉, 복잡한 이벤트 로직 구현에는 한계가 생깁니다.
- 복잡한 로직 흐름에는 부적합합니다.
- UnityEvent는 UI 클릭, 충돌, 상태 변경 등 단순 이벤트 처리에는 적합하지만, 우선순위, 조건 분기, 중복 구독 방지, 타이밍 제어 등이 필요한 고급 이벤트 처리에는 적합하지 않습니다.
- 제네릭 타입 지원이 제한적입니다.
Unity GUI의 Button 스크립트
public class Button : Selectable, IPointerClickHandler, ISubmitHandler
{
[Serializable]
public class ButtonClickedEvent : UnityEvent { }
// Event delegates triggered on click.
[FormerlySerializedAs("onClick")]
[SerializeField]
private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
// ...
private void Press()
{
if (!IsActive() || !lsInteractable())
return;
}
UISystemProfilerApi.AddMarker("Button.onClick", this);
m_OnClick.Invoke();
}'Unity' 카테고리의 다른 글
| [Unity] 디자인 패턴 : Factory 패턴 (0) | 2025.06.16 |
|---|---|
| [Unity] 디자인 패턴 : Command 패턴 (0) | 2025.06.13 |
| [Unity] 디자인 패턴 : State 패턴 (0) | 2025.06.10 |
| [Unity] 디자인 패턴 : SOLID 원칙 (1) | 2025.06.09 |
| [Unity] Unity 2D 게임 개발 정리 (4) (0) | 2025.06.04 |