C#에서 관찰자 모드의 3가지 실현 방식

9381 단어
관찰자 모드라면 정원에서 한 무더기를 찾아낼 수 있을 것 같다.그래서 이 블로그를 쓰는 목적은 두 가지가 있다.
1. 관찰자 모델은 느슨한 결합 코드를 쓰는 필수 모델이다. 중요성은 말하지 않아도 알 수 있다. 코드 차원을 제외하고 많은 구성 요소들이 Publish-Subscribe 모델을 사용했기 때문에 나는 자신의 이해에 따라 사용 장면을 다시 설계하고 관찰자 모델을 유연하게 사용하고 싶다.나는 C#에서 관찰자 모델을 실현하는 세 가지 방안을 총결하고 싶은데, 아직 이런 총결을 보지 못했다.
이제 우리는 이러한 장면을 가정하고 관찰자 모델을 이용하여 수요를 실현한다.
미래의 스마트 홈은 모든 가구에 들어가고 모든 가구에 API를 남겨서 고객에게 사용자 정의 통합을 제공하기 때문에 첫 번째 스마트 알람시계(smartClock)가 먼저 등장한다. 공장측은 이 알람시계에 API를 제공한다. 알람 시간을 설정한 후에 이 알람시계는 이때 통지를 한다. 우리의 스마트 우유 가열기, 빵 오븐, 치약 짜는 설비는 모두 이 알람 소식을 구독하고 주인에게 자동으로 우유, 빵,치약
이 장면은 전형적인 관찰자 모델이다. 스마트 알람시계의 알람은 하나의 주제(subject)이고 우유 가열기, 빵 오븐, 치약 짜는 설비는 관찰자(observer)이다. 그들은 이 주제를 구독하기만 하면 느슨한 결합을 실현할 수 있는 인코딩 모델이다.우리는 세 가지 방안을 통해 하나하나 이 수요를 실현할 것이다.
하나, 이용.net의 Event 모델로 구현
.net의 이벤트 모델은 전형적인 관찰자 모델이다.net 출신 이후 코드에 대량으로 응용되었다. 이벤트 모델이 이런 장면에서 어떻게 사용되는지 살펴보자.
먼저 스마트 알람시계를 소개합니다. 공장에서 간단한 API를 제공했습니다.
 
  
public void SetAlarmTime(TimeSpan timeSpan)
        {
            _alarmTime = _now().Add(timeSpan);
            RunBackgourndRunner(_now, _alarmTime);
        }

Time Span time Span (Time Span) 은 사용자가 시간을 설정하면 알람시계가 백엔드에서while (true) 와 같은 순환 대비 시간을 뛰고 알람 시간이 되면 알림 이벤트를 보냅니다.
 
  
protected void RunBackgourndRunner(Func now,DateTime? alarmTime )
        {
            if (alarmTime.HasValue)
            {
                var cancelToken = new CancellationTokenSource();
                var task = new Task(() =>
                {
                    while (!cancelToken.IsCancellationRequested)
                    {
                        if (now.AreEquals(alarmTime.Value))
                        {
                            //
                            ItIsTimeToAlarm();
                            cancelToken.Cancel();
                        }
                        cancelToken.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(2));
                    }
                }, cancelToken.Token, TaskCreationOptions.LongRunning);
                task.Start();
            }
        }

다른 코드는 중요하지 않으며 알람 시간이 되면 ItIs Time ToAlarm () 을 실행해야 합니다.우리는 구독자에게 알리기 위해 이곳에서 사건을 보냈다.net에서 이벤트 모델을 실현하는 데는 세 가지 요소가 있는데,
1. 테마(subject)에 이벤트,public 이벤트 Action Alarm을 정의합니다.
2. 주제(subject)의 정보에 대한 이벤트ArmEventArgs, 즉 AlarmEventArgs를 정의합니다. 이벤트의 모든 정보를 포함합니다.
3. 테마(subject)는 다음과 같은 방식으로 이벤트를 보냅니다.
 
  
var args = new AlarmEventArgs(_alarmTime.Value, 0.92m);
 OnAlarmEvent(args);

OnAlarmEvent 메서드의 정의
 
  
public virtual void OnAlarm(AlarmEventArgs e)
       {
           if(Alarm!=null)
               Alarm(this,e);
       }

여기에 이벤트 내용-AlarmEventArgs, 이벤트-Alarm(동사, 예를 들어 KeyPress), 이벤트를 트리거하는 방법void OnAlarm()을 주의해야 한다. 이런 요소들은 이벤트 모델의 명칭 규범에 부합해야 한다.
스마트 알람(SmartClock)이 완료되었습니다. 우유 가열기(Milk Schedule)에서 이 Alarm 메시지를 구독합니다.
 
  
public void PrepareMilkInTheMorning()
        {
            _clock.Alarm += (clock, args) =>
            {
                Message =
                    "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                        args.AlarmTime, args.ElectricQuantity*100);
 
                Console.WriteLine(Message);
            };
 
            _clock.SetAlarmTime(TimeSpan.FromSeconds(2));
 
        }

빵 오븐에서도 마찬가지로clock.Alarm+=(clock, args)=>{//it is time to roast bread} 알람 메시지를 구독합니다.
이로써 이벤트 모델에 대한 소개가 끝났을 때 실현 과정이 좀 번거롭고 이벤트 모델의 사용이 부적절하면 memory leak의 문제가 발생할 수 있다. 관찰자(obsever)가 생명주기가 긴 주제(이 주제는 관찰자보다 생명주기가 길다)를 구독했을 때 이 관찰자는 메모리에 회수되지 않는다(주제를 인용하기 때문). 자세한 내용은 Understand Avoiding Memory Leaks with Event Handlers and Event Aggregators,개발자는 이 테마 취소(-=)를 표시해야 합니다.
정원의 A 씨도 약한 인용을 이용하여 이 문제를 해결하는 블로그를 썼다. 사건으로 인한 Memory Leak 문제를 어떻게 해결하는가: Weak Event Handlers.
2. 이용.net에서 IObservable와 IObserver 관찰자 모드 구현
IObservable는 명칭의 의미인 관찰할 수 있는 사물, 즉 주제(subject)와 같이 Observer는 관찰자가 분명하다.
우리의 장면에서 스마트 알람은 IObservable이고 이 인터페이스는 하나의 방법인 IDisposable Subscribe(IObserver observer)만 정의한다.이 방법의 명칭은 약간 어지럽다. Subscribe는 구독이라는 뜻으로 전제에서 왔던 관찰자(observer) 구독 테마(subject)와 다르다.여기서는 테마(subject)가 관찰자(observer)를 구독하는데, 사실 여기서도 통한다. 이 모델에서 테마(subject)는 관찰자(observer) 목록을 유지하기 때문에 테마 구독 관찰자의 말이 있다. 자명종의 IDisposable Subscribe(IObserver observer) 실현을 살펴보자.
 
  
public IDisposable Subscribe(IObserver observer)
        {
            if (!_observers.Contains(observer))
            {
                _observers.Add(observer);
            }
            return new DisposedAction(() => _observers.Remove(observer));
        }

여기 관찰자 리스트가 하나 유지되어 있습니다observers, 알람시계가 시간이 되면 모든 관찰자 목록을 훑어보고 메시지를 관찰자에게 일일이 알립니다
 
  
public override void ItIsTimeToAlarm()
        {
            var alarm = new AlarmData(_alarmTime.Value, 0.92m);
            _observers.ForEach(o=>o.OnNext(alarm));
        }

분명히 관찰자는 OnNext 방법이 있는데 방법 서명은 AlarmData로 통지할 소식 데이터를 대표한다. 다음에 우유 가열기의 실현을 살펴보자. 우유 가열기는 관찰자(observer)로서 IObserver 인터페이스를 실현해야 한다.
 
  
public  void Subscribe(TimeSpan timeSpan)
       {
           _unSubscriber = _clock.Subscribe(this);
           _clock.SetAlarmTime(timeSpan);
       }
 
       public  void Unsubscribe()
       {
           _unSubscriber.Dispose();
       }
 
       public void OnNext(AlarmData value)
       {
                      Message =
                  "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                      value.AlarmTime, value.ElectricQuantity * 100);
           Console.WriteLine(Message);
       }

이외에도 빵 오븐을 편리하게 사용할 수 있도록 Subscribe()와 Unsubscribe()를 두 가지 방법으로 추가하여 호출 과정을 보았다
 
  
var milkSchedule = new MilkSchedule();
//Act
milkSchedule.Subscribe(TimeSpan.FromSeconds(12));

3. Action 함수식 방안
이 방안을 소개하기 전에 이 방안은 관찰자 모델이 아니지만 똑같은 기능을 실현할 수 있고 사용하기에 더욱 세련되고 내가 가장 좋아하는 용법이라는 것을 설명해야 한다.
이 시나리오에서는 스마트 알람(smartClock)이 제공하는 API를 다음과 같이 설계해야 합니다.
 
  
public void SetAlarmTime(TimeSpan timeSpan,Action alarmAction)
       {
           _alarmTime = _now().Add(timeSpan);
           _alarmAction = alarmAction;
           RunBackgourndRunner(_now, _alarmTime);
       }

메소드 서명에서 Action을 수락하려면 알람시계가 도착하면 Action을 실행하면 됩니다.
 
  
public override void ItIsTimeToAlarm()
       {
           if (_alarmAction != null)
           {
               var alarmData = new AlarmData(_alarmTime.Value, 0.92m);
               _alarmAction(alarmData);   
           }
       }

우유 히터에서 사용하는 API도 간단합니다.
 
  
_clock.SetAlarmTime(TimeSpan.FromSeconds(1), (data) =>
            {
                Message =
                   "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                       data.AlarmTime, data.ElectricQuantity * 100);
            });

실제 사용 과정에서 나는 이런 API를 fluent 모델로 설계하여 호출하면 코드가 더욱 뚜렷하다.
스마트 알람(smartClock)의 API:
 
  
public Clock SetAlarmTime(TimeSpan timeSpan)
        {
            _alarmTime = _now().Add(timeSpan);
            RunBackgourndRunner(_now, _alarmTime);
            return this;
        }
 
        public void OnAlarm(Action alarmAction)
        {
            _alarmAction = alarmAction;
        }

우유 가열기에서 호출하기:
 
  
_clock.SetAlarmTime(TimeSpan.FromSeconds(2))
      .OnAlarm((data) =>
                {
                    Message =
                    "Prepraring milk for the owner, The time is {0}, the electric quantity is {1}%".FormatWith(
                        data.AlarmTime, data.ElectricQuantity * 100);
                });

분명히 개선된 작법의 의미가 더 좋다: 알람시계.알람 시간을 설정합니다().경고할 때(()=>{다음 기능 수행})
이런 함수식 문법은 더욱 간결하지만 뚜렷한 단점도 있다. 이 모델은 여러 관찰자를 지원하지 않고 빵 오븐이 이런 API를 사용할 때 우유 가열기의 함수를 덮어씌운다. 즉, 매번 한 관찰자만 사용할 수 있다는 것이다.
종결어, 본문을 총결하였다.net 아래의 세 가지 관찰자 모델 실현 방안은 프로그래밍 장면에서 가장 적합한 모델을 선택할 수 있는 것이 우리의 최종 목표이다.본문 은 본 글 을 다운로드할 때 사용하는 원본 코드 를 제공하는데, 전재 가 필요하면 출처 를 밝혀 주십시오

좋은 웹페이지 즐겨찾기