Coding Memo

Observer Pattern [관찰자 패턴] 본문

etc

Observer Pattern [관찰자 패턴]

minttea25 2023. 6. 11. 16:22

Observer 패턴은 Observable 패턴으로 불리기도 한다.

 

Observer 패턴은 개체 간 1:N 종속성을 설정할 때 사용되는 동작(행태) 디자인 패턴이다.

Subject의 상태가 변경되면 이 Subject에 등록되어 있는 Observer들에게 통지를 해주어 Observer에서 각각의 처리를 할 수 있도록 알려준다.

개제 간에 느슨한 결합을 형성하며, 몇몇 언어들을 자체적으로 간접적 제공(인터페이스 및 클래스)하는 경우도 있다.

 

예시

C# System - IObservable<T>, IObserver<T>
Java java.util.Observable, java.util.Observer

 

Note: 관찰자 패턴은 UI를 업데이트하는데에도 이용할 수 있다. 특정 값이 변경되었을 때, 변화를 감지하고 UI를 자동으로 업데이트 하도록 할 수도 있다!


관찰자 패턴

 

Observer Pattern

 

 

Subject 상태를 유지하고 관찰자에게 알림을 보내는 객체로, 관찰자를 등록, 등록 취소 및 통지의 기능을 포함
Observer Subject가 상태가 변경될 때 알림을 받고자 하는 객체
Register Observer는 알림을 받을 대상에 자신을 등록 (Subscribe, AddObserver ...)
Notification Subject의 상태가 변경되면 등록된 모든 Observer에게 알리고 등록된 각 Observer는 Notify를 통해 특정 메소드를 각각 수행하여 상태 변경과 관련된 작업을 처리

 

Subject의 상태를 관찰할 Observer들을 등록(추가)하고, Subject의 상태가 변경될 때, 등록된 모든 Observer에게 통지를 해주어 Notify 함수를 호출하도록 한다.


UML

 

 

 

NOTE: Subject와 Observer를 인터페이스로 구현할지, 추상 클래스로 구현할지는 적용할 애플리케이션에 맞게 잘 생각하여 사용하자.


구현 (Example)

 

기본적인 인터페이스를 상속한 클래스의 코드는 다음과 같을 것이다.

public interface IObserver
{
    public void Notify();
}

public interface ISubject
{
    public void AddObserver(IObserver observer);
    public void RemoveObserver(IObserver observer);
    public void NotifyObservers();
}

public class ConcreateSubject : ISubject
{
    List<IObserver> _observers = new();

    public void AddObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void NotifyObservers()
    {
        foreach (var observer in _observers)
        {
            observer.Notify();
        }
    }

    public void RemoveObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }
}

public class ConcreateObserver : IObserver
{
    public void Notify()
    {
        // TODO
    }
}

 

이를 이용해 샘플 코드를 작성하여 사용법을 알아보자.

더보기

Subject

public class ConcreateSubject : ISubject
{
    public int N { get; private set; } = 0;

    List<IObserver> _observers = new();

    public void AddN(int dvalue)
    {
        N += dvalue;
        NotifyObservers();
    }

    public void AddObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void NotifyObservers()
    {
        foreach (var observer in _observers)
        {
            observer.Notify();
        }
    }

    public void RemoveObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public override string ToString()
    {
        return $"N={N}";
    }
}

 

Observer

public class ConcreateObserver : IObserver
{
    ISubject _subject;

    public ConcreateObserver(ISubject subject)
    {
        _subject = subject;
        //subject.AddObserver(this); // 여기서 자동으로 등록해줄 수도 있다.
    }

    public void Notify()
    {
        Console.WriteLine($"This is observer... notified from subject: {_subject}");
    }
}

 

Main

public static void Main()
{
    ConcreateSubject subject = new();
    ConcreateObserver observer1 = new(subject);
    ConcreateObserver observer2 = new(subject);

    subject.AddObserver(observer1);
    subject.AddObserver(observer2);

    subject.AddN(10);
}

N값을 변화 시켰을 때 등록된 관찰자들에게 Notify를 한다. (여기서는 AddN이라는 함수를 통해 N 값을 수정했다.)

 

 

출력 결과


관찰자 패턴의 장단점

 

관찰자 패턴도 여러가지 면에서 장점과 단점이 존재한다.

Pros Cons
- Loose Coupling
- Extensibility
- Event-Driven
- Maintainability
- Unintended Updates
- Ordering of Notifications
- Performance Impact
- Debugging Complexity

 

장점

  • Loose Coupling
    Subject는 Observer의 구체적인 내용을 알 필요가 없고, Observer는 Subject와 밀접하게 연결되어 있지 않을 수 있다. 즉, 하나의 구성요소를 변경해도 다른 구성요소에 크게 영향을 미치지 않는 유연한 설계가 가능해진다.

  • Extensibility
    Subject를 수정하지 않고 새로운 Observer를 쉽게 추가할 수 있고 기존 Observer에 영향을 주지 않고 새로운 Subject를 만들 수 있다. (확장성이 좋다.)

  • Event-Driven
    Subject에서 특정 이벤트 또는 상태 변경이 발생하면 Observer에게 자동으로 알리는 이벤트 기반 방식을 사용한다. 이를 통해 개체 간 실시간 업데이트 및 동기화가 가능하다.

  • Maintainability
    Subject와 Observer가 분리되어 있으므로 개별 구성 요소를 수정하거나 테스트하기가 쉬워진다. 한 부분을 변경해도 다름 부분에 파급 효과가 발생할 가능성이 적다. (유지보수에 용이)

 

단점

  • Unintended Updates
    어떤 경우에는 기대하지 않았던 업데이트를 받을 수 있다. 즉, 불필요한 처리가 발생할 수도 있기 때문에 필요한 경우에만 업데이트 되도록 설계를 잘 해야된다.

  • Ordering of Notifications
    명시적으로 지정하지 않는 한, Notify하는 순서는 보장되지 않는다. 신중하게 관리해야 되는 Observer 간에 복잡성이나 순서 종속성을 유발할 수도 있다.

  • Performance Impact
    추가적인 객체간 통신 및 알림 메커니즘으로 약간의 성능 오버헤드가 발생할 수 있다. 다수의 Observer가 등록되어 있거나 상태 변화가 잦은 경우 성능에 영향을 끼칠 수 있다.

  • Debugging Complexity
    많은 Observer와 복잡한 종속성이 있는 시스템에서 Observer Pattern는 시스템 흐름을 복잡하게 보일 수 있게 하여 디버깅하기가 어려워 질 수 있다.

Summary

 

몇몇 언어는 관찰자 패턴을 구현하기 위한 인터페이스나 클래스를 제공하지만 사용자가 원하는 방향대로 프로그래밍하기 위해서는 직접 구현해햐 할때도 많다. 1:N (일대다) 관계가 어떤 시점에서 어떻게 활용되야 할지 고민하고 패턴을 적절히 사용하도록 하자.

'etc' 카테고리의 다른 글

Event Bus Pattern [이벤트 버스 패턴]  (0) 2023.06.18
Command Pattern [커맨드 패턴]  (0) 2023.06.18
Singleton Design Pattern [싱글턴 패턴]  (0) 2023.06.11
OSI, TCP/IP  (0) 2023.06.04
우분투에서 파일 다운로드: wget  (0) 2022.08.16