이 문서에서는 몇 가지 향상된 기능을 갖춘 .NET C#의 관찰자 디자인 패턴에 대해 알아봅니다. 정의 관찰자 디자인 패턴 가장 중요하고 일반적으로 사용되는 디자인 패턴 중 하나입니다. 관찰자 디자인 패턴은 먼저 의 형식적 정의를 확인해 보겠습니다. Observer Design Pattern 에 따라 : 마이크로소프트의 문서 관찰자 디자인 패턴을 사용하면 구독자가 공급자에 등록하고 공급자로부터 알림을 받을 수 있습니다. 푸시 기반 알림이 필요한 모든 시나리오에 적합합니다. 패턴은 공급자(주체 또는 관찰 가능 항목이라고도 함)와 0개, 1개 이상의 관찰자를 정의합니다. 관찰자는 공급자에 등록하고 미리 정의된 조건, 이벤트 또는 상태 변경이 발생할 때마다 공급자는 메서드 중 하나를 호출하여 모든 관찰자에게 자동으로 알립니다. 이 메서드 호출에서 공급자는 관찰자에게 현재 상태 정보를 제공할 수도 있습니다. .NET에서는 일반 및 인터페이스를 구현하여 관찰자 디자인 패턴이 적용됩니다. 일반 유형 매개변수는 알림 정보를 제공하는 유형을 나타냅니다. System.IObservable<T> System.IObserver<T> 따라서 위의 정의를 통해 다음을 이해할 수 있습니다. 우리는 두 개의 당사자 또는 모듈을 가지고 있습니다. 제공할 정보 스트림이 있는 모듈입니다. 이 모듈은 (정보 제공), (정보를 외부 세계에 제공) 또는 (외부 세계에서 관찰할 수 있음)이라고 합니다. Provider Subject Observable 다른 곳에서 오는 정보 스트림에 관심이 있는 모듈입니다. 이 모듈은 (정보를 관찰하므로)라고 합니다. Observer 관찰자 디자인 패턴의 장점 우리가 지금 알고 있듯이 과 모듈 간의 관계를 공식화합니다. 독특하게 만드는 것은 이를 사용하면 긴밀하게 결합된 관계 없이 이를 달성할 수 있다는 것입니다. Observer 디자인 패턴은 Observable Observer 관찰자 디자인 패턴을 패턴이 작동하는 방식을 분석하면 다음을 찾을 수 있습니다. 은 에 대해 필요한 최소한의 정보를 알고 있습니다. Observable Observer 는 에 대해 필요한 최소한의 정보를 알고 있습니다. Observer Observable 상호 지식조차도 구체적인 구현이 아닌 추상화를 통해 달성됩니다. 결국 두 모듈 모두 해당 작업을 수행할 수 있으며 해당 작업만 수행할 수 있습니다. 사용된 추상화 이는 에서 구현하는 데 사용되는 입니다. .NET C# 관찰자 디자인 패턴을 추상화 IObservable<out T> 이것은 모든 나타내는 인터페이스입니다. .NET의 Variance에 대해 더 자세히 알고 싶다면 기사를 확인하세요. . Observable을 Covariant .NET C#의 공분산 및 반공분산 이 인터페이스에 정의된 멤버는 다음과 같습니다. public IDisposable Subscribe (IObserver<out T> observer); 메소드는 일부 정보 스트림에 관심이 있음을 알리기 위해 호출되어야 합니다. Subscribe Observer가 Observable에 메서드는 인터페이스를 구현하는 개체를 반환합니다. 이 객체는 이 제공하는 정보 스트림의 구독을 취소하기 위해 에 의해 사용될 수 있습니다. 이 작업이 완료되면 정보 스트림에 대한 업데이트에 대한 알림을 받지 않습니다. Subscribe IDisposable Observable Observer 관찰자는 IObserver<T> 이는 모든 나타내는 인터페이스입니다. .NET의 Variance에 대해 더 자세히 알고 싶다면 기사를 확인하세요. . Observer 를 Contravariant .NET C#의 공분산 및 반공분산 이 인터페이스에 정의된 멤버는 다음과 같습니다. public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value); 메서드는 에 의해 호출되어 정보 스트림이 완료되었으며 더 이상 정보를 기대하지 않아야 함을 알려야 합니다. OnCompleted Observable Observer에게 Observer가 메소드는 에 의해 호출되어 에게 오류가 발생했음을 알려야 합니다. OnError Observable Observer 메소드는 에 의해 호출되어 에게 새로운 정보가 준비되었으며 스트림에 추가되고 있음을 알려야 합니다. OnNext Observable Observer 마이크로소프트의 구현 이제 C#에서 구현을 권장하는 방법을 살펴보겠습니다. 나중에 제가 직접 구현한 몇 가지 사소한 개선 사항을 보여 드리겠습니다. Microsoft가 관찰자 디자인 패턴 우리는 간단한 구축할 것입니다. 이 애플리케이션에는 모듈(Observable, Provider, Subject)과 모듈(Observer)이 있습니다. 일기예보 콘솔 애플리케이션을 WeatherForecast WeatherForecastObserver 그럼 구현에 대해 살펴보겠습니다. 날씨정보 namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } } 이는 정보 스트림에 흐르는 정보 조각을 나타내는 엔터티입니다. 일기 예보 using System; using System.Collections.Generic; namespace Observable { public class WeatherForecast : IObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IDisposable Subscribe(IObserver<WeatherInfo> observer) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } return new WeatherForecastUnsubscriber(m_Observers, observer); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } } 여기서 우리가 알 수 있는 것은: 클래스는 구현하고 있습니다. WeatherForecast IObservable<WeatherInfo> 메소드 구현에서는 전달된 Observer가 이전에 이미 등록되었는지 확인합니다. 그렇지 않은 경우 로컬 관찰자 목록에 추가합니다. 그런 다음 로컬 목록에 있는 모든 항목을 하나씩 반복하고 Observer의 메서드를 호출하여 Observer에 이에 대해 알립니다. Subscribe m_Observers m_WeatherInfoList WeatherInfo OnNext 마지막으로 정보 스트림 구독을 취소하기 위해 관찰자가 사용할 클래스의 새 인스턴스를 반환합니다. WeatherForecastUnsubscriber 메소드는 메인 모듈이 새로운 등록할 수 있도록 정의됩니다. 실제로 이는 내부 예약 이나 에 대한 수신기 또는 정보 소스 역할을 하는 다른 것으로 대체될 수 있습니다. RegisterWeatherInfo WeatherInfo API 호출 SignalR Hub 구독 취소<T> using System; using System.Collections.Generic; namespace Observable { public class Unsubscriber<T> : IDisposable { private readonly List<IObserver<T>> m_Observers; private readonly IObserver<T> m_Observer; private bool m_IsDisposed; public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer) { m_Observers = observers; m_Observer = observer; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Observers.Contains(m_Observer)) { m_Observers.Remove(m_Observer); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } } 여기서 우리가 알 수 있는 것은: 이는 모든 구독 취소자의 기본 클래스입니다. 적용하여 구현합니다. Disposable Design Pattern을 IDisposable 생성자를 통해 Observer의 전체 목록과 해당 Observer가 생성된 대상을 가져옵니다. 폐기하는 동안 Observer 전체 목록에 Observer가 이미 존재하는지 확인합니다. 그렇다면 목록에서 제거됩니다. 일기예보구독 취소자 using System; using System.Collections.Generic; namespace Observable { public class WeatherForecastUnsubscriber : Unsubscriber<WeatherInfo> { public WeatherForecastUnsubscriber( List<IObserver<WeatherInfo>> observers, IObserver<WeatherInfo> observer) : base(observers, observer) { } } } 여기서 우리가 알 수 있는 것은: 이는 클래스에서 상속됩니다. Unsubscriber<T> 특별한 처리는 이루어지지 않습니다. 일기예보관찰자 using System; namespace Observable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } } 여기서 우리가 알 수 있는 것은: 클래스는 구현하고 있습니다. WeatherForecastObserver IObserver<WeatherInfo> 메서드에서는 콘솔에 온도를 씁니다. OnNext 메서드에서는 콘솔에 "Completed"를 씁니다. OnCompleted 메서드에서는 콘솔에 "Error"를 씁니다. OnError 우리는 메인 모듈이 등록 프로세스를 트리거할 수 있도록 메소드를 정의했습니다. 반환된 구독 취소 개체는 구독 취소 시 사용할 수 있도록 내부에 저장됩니다. void Subscribe(WeatherForecast provider) 동일한 개념을 사용하여 메서드를 정의하고 내부에 저장된 구독 취소 개체를 사용합니다. void Unsubscribe() 프로그램 using System; namespace Observable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } } 여기서 우리가 알 수 있는 것은: 공급자의 인스턴스를 만들었습니다. 그런 다음 3개의 정보를 등록했습니다. 지금까지는 관찰자가 정의되지 않았으므로 콘솔에 아무 것도 기록되어서는 안 됩니다. 그런 다음 관찰자의 인스턴스를 만들었습니다. 그런 다음 관찰자를 스트림에 구독했습니다. 이 순간 콘솔에서 기록된 온도 3개를 찾아야 합니다. 왜냐하면 관찰자가 구독할 때 이미 존재하는 정보에 대해 알림을 받게 되는데 우리의 경우에는 3가지 정보이기 때문입니다. 그런 다음 2가지 정보를 등록합니다. 따라서 콘솔에 2개의 메시지가 더 기록됩니다. 그런 다음 구독을 취소합니다. 그런 다음 1개의 정보를 등록합니다. 그러나 관찰자가 이미 구독을 취소했으므로 이 정보는 콘솔에 기록되지 않습니다. 그런 다음 관찰자는 다시 구독합니다. 그런 다음 1개의 정보를 등록합니다. 따라서 이 정보는 콘솔에 기록됩니다. 마지막으로 이를 실행하면 다음과 같은 결과가 나옵니다. 내 확장 구현 Microsoft의 구현을 확인했을 때 몇 가지 우려 사항을 발견했습니다. 그래서 저는 약간의 변화를 주기로 결정했습니다. IExtendedObservable<out T> using System; using System.Collections.Generic; namespace ExtendedObservable { public interface IExtendedObservable<out T> : IObservable<T> { IReadOnlyCollection<T> Snapshot { get; } IDisposable Subscribe(IObserver<T> observer, bool withHistory); } } 여기서 우리가 알 수 있는 것은: 인터페이스는 인터페이스를 확장합니다. IExtendedObservable<out T> IObservable<T> 입니다. 이에 대해 더 알고 싶다면 기사를 확인하세요. . 공변량 .NET C#의 공분산 및 반공분산 우리는 다른 모듈이 구독하지 않고도 이미 존재하는 정보 항목의 즉시 목록을 얻을 수 있도록 속성을 정의했습니다. IReadOnlyCollection<T> Snapshot 또한 관찰자가 구독하는 순간 이미 존재하는 정보 항목에 대한 알림을 받을지 여부를 결정할 수 있도록 추가 매개 변수를 사용하여 메서드를 정의했습니다. bool withHistory IDisposable Subscribe(IObserver<T> observer, bool withHistory) 구독 취소자 using System; namespace ExtendedObservable { public class Unsubscriber : IDisposable { private readonly Action m_UnsubscribeAction; private bool m_IsDisposed; public Unsubscriber(Action unsubscribeAction) { m_UnsubscribeAction = unsubscribeAction; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing) { m_UnsubscribeAction(); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } } 여기서 우리가 알 수 있는 것은: 이제 클래스는 일반적이지 않습니다. Unsubscriber 정보 엔터티의 유형을 아는 데 더 이상 필요하지 않기 때문입니다. Observer의 전체 목록과 이를 생성한 Observer에 액세스하는 대신 Observable이 폐기될 때 Observable에 알리고 Observable은 등록 취소 프로세스를 자체적으로 처리합니다. 이런 식으로 이전보다 작업량이 줄어들고 본연의 역할만 수행하게 됩니다. 일기예보구독 취소자 using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } } 여기서 우리가 알 수 있는 것은: 에서 부분을 제거했습니다. Unsubscriber<T> <T> 이제 생성자는 폐기 시 호출할 취합니다. Action 일기 예보 using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecast : IExtendedObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IReadOnlyCollection<WeatherInfo> Snapshot => m_WeatherInfoList; public IDisposable Subscribe(IObserver<WeatherInfo> observer) { return Subscribe(observer, false); } public IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); if (withHistory) { foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } } return new WeatherForecastUnsubscriber( () => { if (m_Observers.Contains(observer)) { m_Observers.Remove(observer); } }); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } } 여기서 우리가 알 수 있는 것은: 내부 목록을 으로 반환하는 속성을 제외하면 거의 동일합니다. m_WeatherInfoList IReadOnlyCollection IReadOnlyCollection<WeatherInfo> Snapshot 그리고 매개 변수를 사용하는 메서드입니다. withHistory IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) 일기예보관찰자 using System; namespace ExtendedObservable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this, true); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } } 여기서 알 수 있는 것은 이제 기록으로 할지 여부를 결정하는 제외하면 거의 동일하다는 것입니다. Subscribe Subscribe(WeatherForecast provider) 프로그램 using System; namespace ExtendedObservable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } } 이전과 동일합니다. 마지막으로 이를 실행하면 이전과 동일한 결과가 나타나야 합니다. 무엇 향후 계획 이제 .NET C#의 의 기본 사항을 알게 되었습니다. 그러나 이것이 이야기의 끝은 아닙니다. Observer 디자인 패턴 및 인터페이스를 기반으로 구축되어 유용할 수 있는 더 멋진 기능을 제공하는 라이브러리가 있습니다. IObservable<T> IObserver<T> 이 라이브러리 중에는 도서관. 비동기 프로그래밍을 지원하기 위한 확장 메서드 집합과 LINQ 표준 시퀀스 연산자로 구성됩니다. .NET(Rx)용 반응성 확장 그러므로 이 라이브러리를 탐색하고 시도해 보시기 바랍니다. 나는 당신이 그들 중 일부를 좋아할 것이라고 확신합니다. 여기에도 게시되었습니다 .