본문 바로가기
Software design pattern

C#] Observer Design Pattern(관찰자 디자인 패턴)

by Fastlane 2024. 1. 10.
728x90
반응형

Observer Design Pattern에 대해서

objects 사이의 알림 mechanism을 만들 수 있다. 여러 objects가 다른 object를 관찰하고, 관찰하는 object에 이벤트 발생 시 알림을 받는다. 한쪽에는 관찰되는 object인 Provider가 있고, 다른 한쪽에는 Provider를 관찰하는 하나 이상의 Observers가 있다. 이벤트 또는 상태변경과 같은 미리 지정된 조건 발생 시, Observer는 Provider에 대한 알림을 받을 수 있다. 

 

Observer Design Pattern이 필요한 경우

이 패턴은 application 내부에 분산된 알림 system이 구현되어 있을때 도움이 된다. 이커머스 시스템을 갖고 있다고 하자. 몇몇 고객들은 특정 판매자의 제품에 관심이 있다. 따라서, 매번 새로운 제품을 확인하는 대신에, seller를 관찰하고 실시간 업데이트를 받을 수 있다. 

 

Observer Design Pattern 구현

C#은 Observer pattern 구현을 위한 2가지 interfaces(IObserver<T>, IObservable<T>)를 갖는다. Object가 Provicer가 되려면 IObservable<T>를 구현하고, Observer가 되려면 IObserver<T>를 구현해야 한다. T는 Provider에서 Observer로 전달되는 notification object이다. 

 

  • IObservable<T>
    • IObservable<T>.Subscribe 구현 : 알림을 수신하려는 Observer가 호출하는 메서드
  • IObserver<T>
    • IObserver<T>.OnNext : Observer에게 새 정보나 현재 정보를 제공
    • IObserver<T>.OnError : 오류발생을 Observer에게 알림
    • IObserver<T>.OnCompleted : Observer에게 알림 전송을 완료했음을 나타

 

지원자들이 jobs에 지원할 수 있는 회사를 위한 submission system을 개발한다고 가정하자. 새로운 지원자들이 지원할 때마다, HR 전문가에게 알림을 주고 싶다. 

 

1. Application Model을 정의하자. 

public class Application
{
    public int JobId { get; set; }
    public string ApplicantName { get; set; }

    public Application(int jobId, string applicantName)
    {
        JobId = jobId;
        ApplicantName = applicantName;
    }
}

 

2. HRSpecialist Model을 정의하자. 

public class HRSpecialist
{
    public string Name { get; set; }
    public List<Application> Applications { get; set; }

    public HRSpecialist(string name)
    {
        Name = name;
        Applications = new();
    }

    public void ListApplications()
    {
        if(Applications.Any())
            foreach (var app in Applications)
                Console.WriteLine($"Hey, {Name}! {app.ApplicantName} has just applied for job no. {app.JobId}");
        else
            Console.WriteLine($"Hey, {Name}! No applications yet.");
    }
}

 

3. ApplicationsHandler class를 생성한다. 

Provider로 동작하며, 다음 2 collections을 관리한다. 

  • Applications - 제출된 지원서 리스트
  • _observers - 새로운 지원서를 받을때 받게되는 알림 리스트 

4. Provider 구현

위에서 언급했듯이, Provider는 IObservable<Application>을 구현해야 한다. ApplicationHandler class를 수정해보자. 

public class ApplicationsHandler : IObservable<Application>
{
	//중복이 필요없고, 순서도 상관없으므로 List보다 HashSet이 적합하다.
    private readonly HashSet<IObserver<Application>> _observers = new();
    private readonly HashSet<Application> Applications = new();

    public IDisposable Subscribe(IObserver<Application> observer)
    {
        if (!_observers.Contains(observer))
        {
            _observers.Add(observer);

            foreach (var item in Applications)
                observer.OnNext(item);
        }

        return new Unsubscriber(_observers, observer);
    }

    public void AddApplication(Application app)
    {
    	// 새로운 application 추가
        Applications.Add(app);
		
        // OnNext 함수를 호출하므로, 각 observer에게 알림을 준다. 
        foreach (var observer in _observers)
            observer.OnNext(app);
    }

    public void CloseApplications()
    {
    	// 각 Observer에게 새로운 applications이 없음을 알린다. 
        foreach (var observer in _observers)
            observer.OnCompleted();

        _observers.Clear();
    }
}

 

 

5. Unsubscriber를 정의하자. 

public class Unsubscriber : IDisposable
{
    private readonly ISet<IObserver<Application>> _observers;
    private readonly IObserver<Application> _observer;

    public Unsubscriber(ISet<IObserver<Application>> observers, IObserver<Application> observer)
    {
        _observers = observers;
        _observer = observer;
    }

    public void Dispose()
    {
        if (_observers.Contains(_observer))
            _observers.Remove(_observer);
    }
}

 

Provider가 준비되었다. 이제 HRSpecialist가 AppliationHandler를 subscribe하도록 구성하자. 

 

6. Observer 구현

HRSpecialist class에 _cancellation 추가하고 몇개의 함수도 추가해서 IObserver<Application>을 구현하자. 

public class HRSpecialist : IObserver<Application>
{
    private IDisposable? _cancellation;
    public string Name { get; set; }
    public List<Application> Applications { get; set; }

    public HRSpecialist(string name)
    {
        Name = name;
        Applications = new();
    }

    public virtual void Subscribe(ApplicationsHandler provider)
    {
        _cancellation = provider.Subscribe(this);
    }

    public virtual void Unsubscribe()
    {
        _cancellation?.Dispose();
        Applications.Clear();
    }

    public void OnCompleted()
    {
        Console.WriteLine($"Hey, {Name}! We are not accepting any more applications");
    }

    public void OnError(Exception error)
    {
        // This is called by the provider if any exception is raised, no need to implement it here
    }

    public void OnNext(Application value)
    {
        Applications.Add(value);
        Console.WriteLine($"Hey, {Name}! {value.ApplicantName} has just applied for job no. {value.JobId}");
    }

    public void ListApplications()
    {
        if(Applications.Any())
            foreach (var app in Applications)
                Console.WriteLine($"Hey, {Name}! {app.ApplicantName} has just applied for job no. {app.JobId}");
        else
            Console.WriteLine($"Hey, {Name}! No applications yet.");
    }
}

 

3개의 interfaces함수를 구현했다. 

  • OnNext : notification을 받는다. 
  • OnError : exception 처리 
  • OnCompleted : 더 이상 알림이 없음을 나타낸다. 

이 함수들은 Provider에 의해 호출된다. 

 

Provider의 Subscribe함수를 호출하고 _cancellation에 Unsubscriber object를 할당하는 Subscribe 함수도 추가했다. Unsubscriber 함수에 의해 사용된다. 

 

이제 테스트해보자. 

namespace ObserverPattern;


class Program
{
    static void Main(string[] args)
    {


        var provider = new ApplicationsHandler();


        var observer1 = new HRSpecialist("Bill");
        var observer2 = new HRSpecialist("John");
       
        observer1.ListApplications();
        observer2.ListApplications();


        observer1.Subscribe(provider);
        observer2.Subscribe(provider);


        provider.AddApplication(new(1, "Sam"));
        provider.AddApplication(new(2, "Kim"));


        observer2.Unsubscribe();
        observer2.ListApplications();


       
        provider.AddApplication(new(3, "Lee"));


        provider.CloseApplications();


    }
}

 

output:

Hey, Bill! No applications yet.

Hey, John! No applications yet.

Hey, Bill! Sam has just applied for job no. 1

Hey, John! Sam has just applied for job no. 1

Hey, Bill! Kim has just applied for job no. 2

Hey, John! Kim has just applied for job no. 2

Dispose

Hey, John! No applications yet.

Hey, Bill! Lee has just applied for job no. 3

Hey, Bill! We are not accepting any more applications

 

 

 

728x90
반응형

댓글