옵저버 패턴(Observer Pattern) 이해하기
옵저버 패턴은 객체 간의 일대다 관계를 정의하여, 한 객체의 상태가 변경되면 이를 의존하고 있는 다른 객체들이 자동으로 통지(알림)를 받고 갱신될 수 있도록 하는 행동 디자인 패턴입니다. 이 패턴은 발행/구독(Publish-Subscribe) 모델로도 알려져 있으며, 이벤트 기반 프로그래밍에서 자주 사용됩니다.
옵저버 패턴은 상태 변화와 이를 의존하는 객체들의 동작을 분리하여 느슨한 결합을 구현하는 데 도움을 줍니다. 이번 포스트에서는 옵저버 패턴의 개념, 구조, 구현 방법, 장단점 및 사용 사례를 구체적인 예제와 함께 살펴보겠습니다.
옵저버 패턴이란?
옵저버 패턴은 하나의 객체(주제, Subject)가 상태를 관리하고 변경할 때, 이를 관찰하는 여러 객체(옵저버, Observer)에게 자동으로 알림을 보내는 패턴입니다. 주제는 옵저버들에게 변경 사항을 통지할 뿐, 옵저버가 수행하는 작업에는 관여하지 않습니다.
이 패턴을 사용하면 상태 변경을 관찰하고 처리하는 로직을 주제와 분리하여, 객체 간의 의존성을 줄이고 코드의 유연성과 확장성을 높일 수 있습니다.
옵저버 패턴의 구조
옵저버 패턴은 다음과 같은 구성 요소로 이루어집니다.
- Subject (주제):
- 옵저버를 등록하거나 제거하고, 상태 변경 시 옵저버들에게 알림을 보내는 역할을 합니다.
- Observer (옵저버):
- 주제를 관찰하며, 상태 변경 알림을 받으면 필요한 작업을 수행합니다.
- ConcreteSubject (구체적인 주제):
- 상태를 관리하고, 변경 사항을 옵저버들에게 알리는 주제의 구체적인 구현입니다.
- ConcreteObserver (구체적인 옵저버):
- 알림을 받고 특정 작업을 수행하는 옵저버의 구체적인 구현입니다.
옵저버 패턴의 구현 방법
예제 코드: 날씨 알림 시스템
날씨 정보를 관리하는 시스템에서, 온도 변화가 발생할 때 이를 관찰하는 디스플레이 장치들이 업데이트되는 예제를 살펴보겠습니다.
// Observer 인터페이스
public interface IObserver
{
void Update(float temperature);
}
// Subject 인터페이스
public interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
}
// ConcreteSubject 클래스
public class WeatherStation : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private float _temperature;
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(_temperature);
}
}
public void SetTemperature(float temperature)
{
_temperature = temperature;
Console.WriteLine($"WeatherStation: Temperature updated to {_temperature}");
NotifyObservers();
}
}
// ConcreteObserver 클래스: 모바일 디스플레이
public class MobileDisplay : IObserver
{
private string _name;
public MobileDisplay(string name)
{
_name = name;
}
public void Update(float temperature)
{
Console.WriteLine($"{_name}: Temperature updated to {temperature}°C");
}
}
// ConcreteObserver 클래스: 웹 디스플레이
public class WebDisplay : IObserver
{
public void Update(float temperature)
{
Console.WriteLine($"WebDisplay: Temperature updated to {temperature}°C");
}
}
// Client 코드
class Program
{
static void Main()
{
// 주제 생성
WeatherStation weatherStation = new WeatherStation();
// 옵저버 생성 및 등록
IObserver mobileDisplay1 = new MobileDisplay("MobileDisplay1");
IObserver mobileDisplay2 = new MobileDisplay("MobileDisplay2");
IObserver webDisplay = new WebDisplay();
weatherStation.RegisterObserver(mobileDisplay1);
weatherStation.RegisterObserver(mobileDisplay2);
weatherStation.RegisterObserver(webDisplay);
// 온도 변경
weatherStation.SetTemperature(25.0f);
weatherStation.SetTemperature(30.0f);
// 옵저버 제거 후 상태 변경
weatherStation.RemoveObserver(mobileDisplay1);
weatherStation.SetTemperature(20.0f);
}
}
실행 결과
WeatherStation: Temperature updated to 25
MobileDisplay1: Temperature updated to 25°C
MobileDisplay2: Temperature updated to 25°C
WebDisplay: Temperature updated to 25°C
WeatherStation: Temperature updated to 30
MobileDisplay1: Temperature updated to 30°C
MobileDisplay2: Temperature updated to 30°C
WebDisplay: Temperature updated to 30°C
WeatherStation: Temperature updated to 20
MobileDisplay2: Temperature updated to 20°C
WebDisplay: Temperature updated to 20°C
코드 설명
- Subject:
- ISubject 인터페이스는 옵저버를 등록(RegisterObserver), 제거(RemoveObserver), 통지(NotifyObservers)하는 메서드를 정의합니다.
- WeatherStation 클래스는 온도 상태를 관리하며, 상태 변경 시 등록된 옵저버들에게 알립니다.
- Observer:
- IObserver 인터페이스는 상태 변경 알림을 처리하는 Update() 메서드를 정의합니다.
- MobileDisplay와 WebDisplay는 알림을 받고, 변경된 온도를 출력하는 구체적인 옵저버입니다.
- Client:
- 클라이언트는 WeatherStation 객체에 옵저버를 등록하고, 온도를 변경하여 옵저버들에게 알림을 전송합니다.
옵저버 패턴의 장단점
장점
- 느슨한 결합:
- 주제와 옵저버가 서로 직접적으로 참조하지 않으므로, 객체 간의 결합도가 낮아집니다.
- 자동화된 갱신:
- 주제의 상태 변경 시, 등록된 옵저버들이 자동으로 알림을 받고 업데이트됩니다.
- 확장성:
- 새로운 옵저버를 쉽게 추가하거나 제거할 수 있어 확장성이 뛰어납니다.
단점
- 성능 문제:
- 옵저버의 수가 많거나 복잡한 작업을 수행할 경우, 상태 변경 시 성능 저하가 발생할 수 있습니다.
- 예측 어려움:
- 옵저버가 주제의 상태를 변경하면서 추가적인 알림이 발생할 경우, 시스템 동작이 복잡해질 수 있습니다.
- 의존성 숨김:
- 옵저버가 주제의 상태에 의존하지만, 이 의존성이 코드에서 명시적으로 드러나지 않아 디버깅이 어려울 수 있습니다.
언제 옵저버 패턴을 사용해야 할까?
옵저버 패턴은 다음과 같은 상황에서 유용합니다.
- 다수의 객체가 특정 객체의 상태 변화에 반응해야 할 때:
- 한 객체의 상태 변경이 여러 객체에 영향을 미쳐야 하는 경우.
- 상태 변경 알림이 필요할 때:
- UI 업데이트, 알림 시스템, 데이터 동기화 등 상태 변화에 따라 다른 작업이 필요할 때.
- 발행/구독 모델이 필요한 경우:
- 이벤트 기반 시스템에서 발행자와 구독자 간의 느슨한 결합을 유지해야 할 때.
옵저버 패턴과 관련 패턴 비교
옵저버 패턴은 상태 변경과 알림을 처리하는 데 초점을 둡니다. 유사한 디자인 패턴과의 차이를 살펴보겠습니다.
- 이벤트 디스패처:
- 옵저버 패턴과 유사하지만, 일반적으로 언어에서 제공하는 이벤트/델리게이트를 사용하여 구현됩니다.
- 미디에이터 패턴:
- 객체 간의 상호작용을 중재자가 관리합니다. 옵저버 패턴은 상태 변경 알림에 초점이 있습니다.
- 퍼블리셔-서브스크라이버 패턴:
- 옵저버 패턴의 일반화된 형태로, 메시지 브로커를 통해 발행자와 구독자 간의 의존성을 제거합니다.
실무에서 옵저버 패턴 활용하기
옵저버 패턴은 다음과 같은 실무 상황에서 널리 사용됩니다.
- UI 업데이트:
- 버튼 클릭, 상태 변경 시 UI 요소를 자동으로 갱신.
- 알림 시스템:
- 사용자 알림, 이메일 전송, 로그 기록 등 다양한 작업 처리.
- 데이터 동기화:
- 모델-뷰-컨트롤러(MVC) 구조에서 모델 변경 시 뷰 업데이트.
- 이벤트 처리:
- 게임 엔진에서 캐릭터 상태 변경 시 이벤트 처리.
마무리하며
옵저버 패턴은 객체 간의 느슨한 결합을 유지하면서 상태 변경과 알림을 처리할 수 있는 강력한 패턴입니다. 특히 이벤트 기반 시스템과 같이 상태 변경을 자동으로 처리해야 하는 애플리케이션에서 큰 효과를 발휘합니다.
객체 간의 결합도를 낮추고, 상태 변경에 유연하게 대응해야 하는 시스템을 설계할 때 옵저버 패턴을 활용해보세요. 이를 통해 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다.
'Thinking > Concept' 카테고리의 다른 글
전략 패턴(Strategy Pattern) 이해하기 (0) | 2024.11.17 |
---|---|
상태 패턴(State Pattern) 이해하기 (0) | 2024.11.17 |
메멘토 패턴(Memento Pattern) 이해하기 (1) | 2024.11.16 |
미디에이터 패턴(Mediator Pattern) 이해하기 (0) | 2024.11.16 |
이터레이터 패턴(Iterator Pattern) 이해하기 (0) | 2024.11.16 |