옵저버 패턴
객체를 가볍게 만들어 메모리 사용량을 줄이는 패턴. 자주 변하는 속성과 불변 속성을 분리하고 불변 속성을 재사용함으로써 메모리 사용량을 줄일 수 있다.
구조
- Subject - Observer(감시자)를 을 알고 있는 구독자. 임의의 개수의 감시자 객체는 Subject를 감시한다. Subject의 상태가 변경되면 Observer에게 알림을 보낸다.
- Observer - Subject의 상태 변화를 갱신하는 데 필요한 인터페이스를 정의한다. Subject의 변경에 따라 변화되어야 하는 객체들의 일관성이 유지된다.
- ConcreteSubject - ConcreteObserver 객체에게 알려주어야 하는 상태를 저장한다. 상태가 변경되면 해당 ConcreteObserver에 변경을 통보한다.
- ConcreteObserver - ConcreteSubject 객체에 대한 참조를 관리한다. Subject의 상태 변화에 따른 구체적인 실행 로직을 작성한다.
기본 인터페이스
// Observer 인터페이스
public interface IObserver
{
void Update(ISubject subject);
}
// Subject 인터페이스
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
// ConcreteSubject 클래스
public class WeatherStation : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private int _temperature;
public int Temperature
{
get => _temperature;
set
{
_temperature = value;
Notify();
}
}
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify()
{
foreach (var observer in _observers)
{
observer.Update(this);
}
}
}
// ConcreteObserver 클래스
public class PhoneDisplay : IObserver
{
public void Update(ISubject subject)
{
if (subject is WeatherStation weatherStation)
{
Console.WriteLine($"WeatherStation has new temperature: {weatherStation.Temperature}");
}
}
}
public class Program
{
public static void Main()
{
WeatherStation weatherStation = new WeatherStation();
PhoneDisplay phoneDisplay = new PhoneDisplay();
weatherStation.Attach(phoneDisplay);
weatherStation.Temperature = 30; // 옵저버에게 알림이 간다
weatherStation.Detach(phoneDisplay);
weatherStation.Temperature = 25; // 옵저버에게 알림이 가지 않는다
}
}
왜 사용해야 할까?
시스템을 구현하다 보면, 서로 다른 객체들이 연관되어 영향을 주는 시스템을 구현해야 하는 경우가 종종 있다. 어떤 객체의 상태가 변화할 시 다른 객체가 이를 파악하고 대응을 하도록 작성해야 한다.
게임 개발에서 흔히 발생하는 상황을 예로 들어보겠다. 게임에서 캐릭터의 레벨업 시스템을 구현한다고 가정해보자. 캐릭터가 레벨업을 하면 이는 능력치 증가, 장비 착용 가능 여부, 새로운 스킬의 잠금 해제, 퀘스트의 진행 조건 변경 등 다양한 게임 내 시스템에 영향을 미친다.
전통적인 구현 방식에서 캐릭터 클래스는 모든 관련 시스템과 직접적으로 연결되어 있어야 한다. 예를 들어, 캐릭터 클래스는 능력치 시스템, 장비 시스템, 스킬 시스템 등에 직접 알림을 보내야 한다.
이는 각 시스템이 캐릭터 클래스의 변화에 매우 의존적이 되며, 이는 높은 결합도를 의미한다. 높은 결합도는 시스템 간의 유연성을 저하시키고, 새로운 기능이나 시스템의 추가, 기존 시스템의 수정을 어렵게 만든다. 새로운 기능을 추가하려면 캐릭터 클래스와 연결된 모든 시스템을 이해하고 수정해야 하며, 이는 개발 과정을 복잡하게 만들고 버그를 발생시킬 위험을 증가시킨다. 또한, 한 시스템의 변경이 다른 시스템에 예기치 않은 영향을 미칠 수 있어 유지보수가 어려워진다.
옵저버 패턴을 적용하면 이러한 문제들을 해결할 수 있다. 옵저버 패턴에서 캐릭터 클래스는 'Subject' 역할을 하며, 다양한 게임 시스템은 'Observer'로서 동작한다. 캐릭터 클래스의 상태가 변경될 때마다, 예를 들어 레벨업이 발생하면, 해당 클래스는 자신에게 등록된 모든 옵저버들에게 변경 사항을 알린다.
// 캐릭터 클래스는 Subject 역할을 함
public class Character : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private int _level;
public int Level
{
get { return _level; }
set
{
_level = value;
Notify(); // 레벨 업이 발생하면 옵저버들에게 알림
}
}
/** ...skip */
public void Notify()
{
foreach (var observer in _observers)
{
observer.Update();
}
}
}
// 캐릭터 생성
Character character = new Character();
// 다양한 시스템 생성 및 캐릭터에 등록
LevelUpSystem levelUpSystem = new LevelUpSystem(character);
HealthSystem healthSystem = new HealthSystem(character);
// 캐릭터 레벨 업
character.Level++;
// 캐릭터 레벨 업 결과 확인
// 출력:
// Level Up System: Character leveled up!
// Health System: Character's health updated!
각 옵저버는 이 정보를 받아 자신의 상태를 업데이트한다. 이를 통해 각 시스템은 캐릭터 클래스와의 결합도를 낮출 수 있으며, 캐릭터 클래스의 변경 없이도 새로운 시스템을 추가하거나 기존 시스템을 변경할 수 있다.
옵저버 패턴의 적용은 시스템의 유연성을 크게 향상시킨다. 새로운 기능이나 시스템을 추가할 때 기존 코드를 거의 변경할 필요가 없어진다. 각 시스템은 독립적으로 작동하며, 캐릭터 클래스의 변경이 필요 없기 때문에 개발과 유지보수가 용이해진다. 또한 각 시스템의 재사용성도 높아진다. 특정 시스템이 다른 게임이나 다른 부분에서 필요할 때, 해당 옵저버만을 재사용하면 된다. 이러한 구조는 유지보수를 단순화한다. 한 시스템을 수정하더라도 다른 시스템에 영향을 미치지 않기 때문에, 시스템 간의 의존성으로 인한 복잡성과 버그 발생 가능성이 크게 줄어든다.
장점
- 상태를 변경하는 객체(publisher)와 변경을 감지하는 객체(subsriber)의 관계를 느슨하게 유지할 수 있다.
- 런타임에 옵저버를 추가하거나 제거할 수 있다.
- Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
단점
- 코드 복잡도가 증가한다.
- 다수의 Observer 객체를 등록 이후 해지 않는다면 memory leak이 발생할 수도 있다.
참고자료
GoF의 디자인 패턴 - 예스24
이 책은 디자인 패턴을 다룬 이론서로 디자인 패턴의 기초적이고 전반적인 내용을 학습할 수 있다.
www.yes24.com
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
23. [Design Pattern] 템플릿 메소드 패턴(Templete Method) (0) | 2024.02.28 |
---|---|
22. [Design Pattern] 전략 패턴(Strate) (0) | 2024.02.27 |
20. [Design Pattern] 상태 패턴(State) (0) | 2024.02.20 |
19. [Design Pattern] 메멘토(Memento) 패턴 (0) | 2024.02.19 |
18. [Design Pattern] 중재자(Mediator) 패턴 (0) | 2024.01.22 |