[Unity] 디자인 패턴 - 옵저버(Observer) 패턴 이해하기
Unity에서 Observer 패턴은 이벤트 중심의 시스템을 구축하는 데 매우 유용한 디자인 패턴입니다. 이 글에서는 Observer 패턴의 개념, Unity에서의 구현 방법, 장단점, 실제 프로젝트에서의 활용 사례 등을 다루어 보겠습니다.
Observer 패턴이란?
Observer 패턴은 객체지향 디자인 패턴 중 하나로, 객체의 상태 변화를 관찰하는 관찰자(Observer)들이 주체(Subject)의 상태 변화를 감지하고 자동으로 업데이트되는 구조를 제공합니다. 주체는 자신의 상태가 변할 때마다 등록된 모든 관찰자에게 알림을 보냅니다.
Unity에서의 Observer 패턴
Unity에서는 다양한 이벤트를 처리하기 위해 Observer 패턴을 자주 사용합니다. 예를 들어, 버튼 클릭 이벤트를 처리하거나, 게임 객체의 상태 변화를 여러 시스템에 통지하는 경우에 유용하게 사용됩니다.
주요 구성 요소
- Subject (주체): 상태를 가지고 있으며, 상태 변화를 관찰자들에게 통지합니다.
- Observer (관찰자): 주체의 상태 변화를 관찰하고, 상태 변화에 따라 특정 작업을 수행합니다.
예제 코드: Unity에서의 Observer 패턴 구현
다음은 Unity에서 Observer 패턴을 구현한 예제입니다. 이 예제는 버튼을 클릭하면 다양한 액션(소리 재생, 파티클 생성, 애니메이션 실행)이 수행되는 구조를 보여줍니다.
1. Subject 클래스
using System;
using System.Collections.Generic;
using UnityEngine;
public class ButtonSubject : MonoBehaviour
{
// Observer 리스트를 관리하는 리스트
private List<IObserver> observers = new List<IObserver>();
// Observer 추가
public void AddObserver(IObserver observer)
{
observers.Add(observer);
}
// Observer 제거
public void RemoveObserver(IObserver observer)
{
observers.Remove(observer);
}
// 상태 변화 통지
public void NotifyObservers()
{
foreach (IObserver observer in observers)
{
observer.Update();
}
}
// 버튼 클릭 메서드
public void Click()
{
Debug.Log("Button clicked!");
NotifyObservers();
}
}
2. Observer 인터페이스
public interface IObserver
{
void Update();
}
3. 구체적인 Observer 클래스들
// 파티클 시스템 Observer
public class ParticleSystemObserver : MonoBehaviour, IObserver
{
public ParticleSystem particleSystem;
public void Update()
{
particleSystem.Play();
Debug.Log("Particle system activated!");
}
}
// 오디오 Observer
public class AudioObserver : MonoBehaviour, IObserver
{
public AudioSource audioSource;
public void Update()
{
audioSource.Play();
Debug.Log("Playing sound!");
}
}
// 애니메이션 Observer
public class AnimationObserver : MonoBehaviour, IObserver
{
public Animator animator;
public void Update()
{
animator.SetTrigger("PlayAnimation");
Debug.Log("Animation started!");
}
}
4. 메인 클래스
public class ObserverPatternExample : MonoBehaviour
{
public ButtonSubject button;
public ParticleSystemObserver particleObserver;
public AudioObserver audioObserver;
public AnimationObserver animationObserver;
void Start()
{
// Observer 등록
button.AddObserver(particleObserver);
button.AddObserver(audioObserver);
button.AddObserver(animationObserver);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
button.Click();
}
}
}
예제 설명
- ButtonSubject 클래스는 주체 역할을 합니다. 이 클래스는 IObserver 리스트를 가지고 있으며, AddObserver와 RemoveObserver 메서드를 통해 관찰자를 추가하거나 제거할 수 있습니다. NotifyObservers 메서드는 버튼이 클릭되었을 때 등록된 모든 관찰자에게 상태 변화를 통지합니다.
- IObserver 인터페이스는 관찰자들이 구현해야 할 Update 메서드를 정의합니다.
- ParticleSystemObserver, AudioObserver, AnimationObserver 클래스는 구체적인 관찰자들로, Update 메서드가 호출될 때 각각의 동작을 수행합니다.
- ObserverPatternExample 클래스는 실행 예제입니다. 주체인 버튼을 생성하고, 여러 관찰자를 등록합니다. 버튼을 클릭할 때마다 모든 관찰자들이 자신의 동작을 수행합니다.
Observer 패턴의 장단점
장점
- 느슨한 결합: 주체와 관찰자는 서로 강하게 결합되어 있지 않기 때문에, 독립적으로 변경 및 확장이 용이합니다.
- 재사용성: 새로운 관찰자를 쉽게 추가할 수 있으며, 기존 코드를 수정하지 않고도 기능을 확장할 수 있습니다.
- 단일 책임 원칙: 각 클래스는 자신의 역할에 집중하여, 유지보수가 용이합니다.
단점
- 복잡성 증가: 시스템이 복잡해질 수 있으며, 등록된 관찰자의 순서가 예측 불가능할 수 있습니다.
- 메모리 누수: 잘못된 등록 해제로 인해 메모리 누수가 발생할 수 있습니다.
- 성능 문제: 많은 관찰자가 등록된 경우 성능 문제가 발생할 수 있습니다.
- 디버깅 어려움: 여러 관찰자가 이벤트를 처리할 때 디버깅이 어려울 수 있습니다.
- 순환 참조 문제: 순환 참조로 인해 무한 루프에 빠질 수 있습니다.
다양한 사용 사례
Observer 패턴은 다양한 상황에서 유용하게 사용될 수 있습니다:
- GUI 이벤트 처리: 버튼 클릭, 마우스 움직임 등의 이벤트 처리에 사용됩니다.
- 모델-뷰-컨트롤러(MVC) 구조: 모델의 상태 변화가 뷰에 자동으로 반영되도록 합니다.
- 알림 시스템: 상태 변화가 여러 모듈에 통지되어야 하는 시스템에서 사용됩니다.
- 게임 개발: 게임 객체의 상태 변화(예: 체력 감소, 레벨 업 등)를 다양한 시스템에 통지하는 데 사용됩니다.
실제 프로젝트 적용 사례
실제 프로젝트에서 Observer 패턴을 적용하는 사례를 소개합니다. 예를 들어, 게임 개발 프로젝트에서 캐릭터의 상태 변화(체력, 경험치 등)를 UI와 다른 시스템(예: 로그 시스템)에 통지하는 데 Observer 패턴을 사용한 경험을 공유할 수 있습니다.
유닛 테스트 작성
Observer 패턴을 구현한 코드에 대해 유닛 테스트를 작성하여, 각 기능이 정상적으로 동작하는지 검증할 수 있습니다.
using NUnit.Framework;
[TestFixture]
public class ObserverPatternTests
{
[Test]
public void TestObserverPattern()
{
// 주체 생성
ButtonSubject button = new GameObject().AddComponent<ButtonSubject>();
// Mock Observer 생성 및 주체에 등록
MockObserver observer = new GameObject().AddComponent<MockObserver>();
button.AddObserver(observer);
// 버튼 클릭
button.Click();
// Mock Observer가 호출되었는지 확인
Assert.IsTrue(observer.WasUpdated);
}
}
public class MockObserver : MonoBehaviour, IObserver
{
public bool WasUpdated { get; private set; }
public void Update()
{
WasUpdated = true;
}
}
Best Practices
Observer 패턴을 구현할 때 다음과 같은 모범 사례를 따르는 것이 좋습니다:
- 관찰자의 수를 최소화: 불필요한 관찰자가 등록되지 않도록 관리합니다.
- 정확한 등록/해제 관리: 메모리 누수를 방지하기 위해 등록과 해제를 정확히 관리합니다.
- 디버깅 도구 사용: 디버깅을 쉽게 하기 위해 로깅이나 디버깅 도구를 활용합니다.
- 순환 참조 방지: 순환 참조가 발생하지 않도록 주의하여 설계합니다.
Observer 패턴은 이벤트 중심의 시스템에서 매우 유용하게 사용될 수 있는 강력한 도구입니다. 그러나 복잡성을 증가시키고 디버깅이 어려울 수 있는 단점이 있으므로, 설계 시 신중한 접근이 필요합니다. 이 글이 Unity에서 Observer 패턴을 이해하고 적용하는 데 도움이 되기를 바랍니다.