소프트웨어/C# & ASP.NET

닷넷 3.5를 이용한 디자인 패턴의 구현 (4)

falconer 2009. 8. 5. 08:00
이름에 짐작할 수 있듯이, Observer 패턴은 오브젝트의 상태를 관잘하는 데 사용된다. 이 패턴이 약간 변형된 패턴이 출판 (Publish)과 구독(Subscribe)이다 관찰 대상 오브젝트는 어떤 이벤트 혹은 이벤트들을 "출판"하고 이들을 주시하고 있던 오브젝트들(관찰자들)은 발생한 이벤트를 "구독"한다.

이것을 좀 더 단순화하기 위해, 두 패턴을 모두  Observer 패턴이라고 부를 것이다. 사실 이 두 가지 패턴의 차이는 보는 관찰 시점의 차이일 뿐이다. 쉽게 얘기해서, '당신이 나를 관찰하고 있나요? 와 내가 당신이 구독하려는 나의 이벤트를 출판하고 있나요? 의 차이와 같다. 전자는 나를 보는 관점이 타인이 시점이고 후자는 자신의 시점에서 일어나는 이벤트를 관찰하게 된다.

Observer 패턴은 오브젝트들 사이에 일대다 의존성을 갖는다. 그래서 , 하나의 오브젝트가 상태를 변경하면, 이것에의존하는 모두에게 변경 사실이 통보되고 변경에 대해 반응할 수 있는 기회를 가진다

이패턴의 기본 개념은 관찰자(Observer) 혹은 청취자(listener)라고 불리는 오브젝트들이 주제(subject)라고 불리는 관찰 대상 오브젝트에서 일어날지도 모르는 이벤트를 관찰하기 위해 등록되거나 스스로 등록하는 것이다.


Observer 패턴을 펴현한 UML 클래스 다이어그램




좀 더 확실히 하기 위해서, 실제 생활 속의 예를 들어볼 것이다. 많은 사람들이 블로그를 읽는다. 아직까지 방문해 본 적이 없다면, 지금부터 시작하면 된다. 이들 중 일부는 Slashdot의 오늘의요약 정보를 구독할지도 모른다. 이 사이트는 Observer패턴에 대해 알아야 할 것의 모든 것을 보여준다. Slashdot은 출판하고 사람들은 구독한다. 혹은 관점을 바꿔서, 사람들을 관찰하고 Slashdot은 관찰된다.


관찰의 주제




예제를 통해 알아보는 Observer 패턴


비행 출발과 항공 교통을 처리하는 약간 단순한 Observer 패턴을 적용한 프로그램을 만들어 보자. 이 예제는 패턴을 구성하는 네 가지 구성요소가 있다

주제(subject)와 관찰자(observer)는 실제로 사용하기 위해 필요한 메소드와 변수를 정의하는 인터페이스이고 이를 구현한 것이 실제주제(Concrete subject)와 실제관찰자(concrete observer) 이다

Subject
Subject는 자신의 관찰자를 알고 있고, 이들을 추가했다 삭제할 수 있는 인터페이스를 제공한다. 얼마나 많은 관찰자 오브젝트들이 이 주제(subject)를 관찰할지 모른다.

Concrete subject
실제 주제(concrete subject)는 고나심 상태(state)를 실제 관찰자 오브젝들에 저장한다. 그리고 상태 변경에 따라 상태가 변경되었다는 적절한 통지를 보낸다

Observer
관찰자(observer)는 주제가 변경되었을 때 이 변경 내용을 통지 받을 오브젝트들을 위한 수정 인터페이스를 정의한다

Concrete observer
실제 관찰자(concrete observer)는 실제 주제 오브젝트의 참조를 유지한다. 추가적으로, 주제의 상태와 동기화되기 위해 별도의 상태를 저장하고 관찰자 수정 인터페이스의구현을 제공한다


먼저, AirlineSchedule 클래스라는 예제를 사용해서 주제(subject)를 구현할 것이다. 누가 봐도 이클래스의 생성자는 아주 직관적이다. 여기에는 항공사의 이름, 출발 도시, 도착 도시, 출발 시간의 정보를 가지고 있다.





이 클래스는 네 개의 프로퍼티를 선언하는데, 야간 특이한 것이 하나 있다. 이는 바로 DepartureDateTime프로퍼티인테 , 이 프로퍼티는 멤버 변수를 설정할 뿐만 아니라 OnChange()이벤트를 일으킨다

이때 발생하는 이벤트는 다음과 같다

public event ChangeEventHandler<AirlineSchedule , ShangeEventArgs> Change;

// Change 이벤트를 일으킨다

public virtual void OnChange(ChangeEventArgs e)
{
if(Change != null)
{
                        Change(this, e);
            }
}



주제의 핵심 기능은 관찰자(Observer)를 추가하고 삭제할 수 있는 인터페이스르 ㄹ제공하는 것이다 이를 위해 다음 처럼 Attach()와 Detach()메소드를 사용한다.

public void Attach(AirTrafficControl airTrafficControl)
{
     Change +=new ChangeEventHandler<AirlineSchedule, ChangeEventArgs> (airTrafficControl.Update);
}

public void Detach(AirTrafficControl airTrafficControl)
{
     Change -= new ChangeEvenHandeler<AirlineSchedule, ChangeEventArgs> (airTrafficeControl.Update);
}

실제 주제(concrete subject)클래스는 관심 상태를 관찰자들에게 제공한다. 또한 기반 클래스(주로, 주제 클래스)의 Notify() 메소드를 호출해서 모든 관찰자에게 통지를 보낸다.
이를 위해 다음과 같이 간단한 예제를 제공할 것이다

// 설제 주제 (concrete subject)
class CarrierSchedule : AirlineSchedule
{
     // 제시와 알렉스는 정해진 한 장소로 가는 비행기를 타려고 한다.
     public CarrierSchedule(string name, DateTime departing) : base(name,"Boston","Seattle", departing)
    {
    }
}


관찰자 클래스는 모든 관찰자들을 위해 수정 인터페이스를 제공하며, 이를 통해 관찰자들이 주제(subject)에서
수정 통지를 받을 수 있도록 해준다. 그래서, 주제에 관심을 지켜보려는 관찰자들은 이 인터페이스를 구현해야 한다. 이 인터페이스는 다음과 같은 Update()라는 단일 메소드를 구현할 필요가 있다.


interface IATC
{
    void Update(AirlineSchedule sender, ChangeEventArgs e);
}

각 실제 관찰자(concrete observer)는 주제는 상태가 변경될 때 통지를 받을 수 있도록 실제 주제(concrete subject)에 팜조를 유지한다.

확인할 수 있듯이,  실제 관찰자 클래스에서 Update() 메소드를 재정의한다. 주제가 Update()메소드를 호출할 떄, 실제 관찰자(concrete observer)는 Update()를 구현하고 변경 통지가 일어낫을 떄 이를 처리할 독자적인 로직을 정의한다.


//실제 관찰자 (concrete observer)
class AirTrafficeControl : IATC
{
   public string Name {get; set;}
   public CarrierSchedule CarrierSchedule{get;set;}
 
   // 생성자
   public AirTrafficControl(stirng name)
   {
      this.Name = name;
   }

   public void Update(AirlineSchedule sender, ChangeEventArgs e)
   {
      Console.WriteLine("{0} Air Traffic Control Notified:\n {1}'s flight 497 from {2} " +
      "to {3} new departure time :{4:hh:mmtt}", Name, e.Airline, e.DepartureAirport, 
       e.ArrivalAirport, e.DepartureDateTime);
       Console.WriteLine("---------------");
    }
 
}



코드에서 실행하기


이 Observer 패턴을 실행해 보기 위해, 지금까지 만든 클래스들을 사용할 별도의 코드가 필요할 것이다. 다음은 간단한 콘솔 프로그램의 전체 코드이다

class Program
{

   static void Main()
  {
       DateTitme now = DateTime.Now;
       // 출발 시간과 목적지가 있는  새로운 비행편을 만든다.
       CarrierSchedule jetBlue = new CarrierSchedule("JetBlue", noew);
       JetBlue.Attach(new AirTrafficControl("Boston"));
       JetBlue.Attach(new AirTrafficControl("Seattle"));

        // ATC 클래스들은 출발 시간의 연기를 통지할것이다

        jetBlue.DepartureDateTime = now.AddHours(1.25); //날씨 때문에 연기
        jetBlue.DepartureDateTime = now.AddHours(2.75); // 날씨가 더 악화됨
        jetBlue.DepartureDateTime = now.AddHours(3.5); //보안 때문에 연기
        jetBlue.DepartureDateTitme = now.AddHours(3.75); // 시애틀 출발시간

       // 승객 대기

       console.Read();
   }
}



관찰자 패턴을 실행할 C# 콘솔 프로그램의 전체 코드와 파일




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Observer
{

    class Program
    {
        static void Main()
        {
            DateTime now = DateTime.Now;

            //출발 시간과 목적지가 있는  새로운 비행편을 만든다

            CarrierSchedule jetBlue = new CarrierSchedule("JetBlue", now);
            jetBlue.Attach(new AirTrafficControl("Boston"));
            jetBlue.Attach(new AirTrafficControl("Seattle"));
            //ATC 클래스들은 출발 시간의 연기를 통지할 것이다
            jetBlue.DepartureDateTime = now.AddHours(1.25);// 날씨 때문에 연기
            jetBlue.DepartureDateTime = now.AddHours(1.75); //날씨가 더 악화됨
            jetBlue.DepartureDateTime = now.AddHours(0.5); //보안 떄문에 연기
            jetBlue.DepartureDateTime = now.AddHours(0.75); //시애틀 출발 시간

            // 승객 대기
            Console.Read();
        }
    }
    // 비행 스케줄 요청을 가로채기 위한 제네릭 델리게이트 타입 선언
    public delegate void ChangeEventhandler<T, U>(T sender, U eventArgs);

    // 실행에 맞는 이벤트 인수를 설정

    public class ChangeEventArgs : EventArgs
    {
        public string Airline { get; set; }
        public string DepartureAirport { get; set; }
        public string ArrivalAirport { get; set; }
        public DateTime DepartureDateTime { get; set; }

        public ChangeEventArgs(string name, string outAirport, string inAirport, DateTime leaves)
        {
            this.Airline = name;
            this.DepartureAirport = outAirport;
            this.ArrivalAirport = inAirport;
            this.DepartureDateTime = leaves;
        }


    }  // 주제 : Air Traffic Control 센터에서 관찰하게 되는 대상이다
    abstract class AirlineSchedule
    {
        public string Name { get; set; }
        public string DepartureAirport { get; set; }
        public string ArrivalAirport { get; set; }
        private DateTime departuredateTime;

        public AirlineSchedule(string ariline, string outAirport, string inAiport, DateTime leaves)
        {
            this.Name = ariline;
            this.DepartureAirport = outAirport;
            this.ArrivalAirport = inAiport;
            this.departuredateTime = leaves;
        }

        // 이벤트

        public event ChangeEventhandler<AirlineSchedule, ChangeEventArgs> Change;

        // Change 이벤트를 일으킨다
        public virtual void OnChange(ChangeEventArgs e)
        {
            if (Change != null)
            {
                Change(this, e);
            }
        }

        // 관찰자(observer)들을 실제로 추가하는 부분
        public void Attach(AirTrafficControl airTrafficControl)
        {
            Change += new ChangeEventhandler<AirlineSchedule, ChangeEventArgs>(airTrafficControl.Update);
        }

        public void Detach(AirTrafficControl airTrafficControl)
        {
            Change -= new ChangeEventhandler<AirlineSchedule, ChangeEventArgs>(airTrafficControl.Update);
        }

        public DateTime DepartureDateTime
        {
            get { return departuredateTime; }
            set
            {
                departuredateTime = value;
                OnChange(new ChangeEventArgs(this.Name, this.DepartureAirport, this.ArrivalAirport, this.departuredateTime));
                Console.WriteLine("<br/>");
            }
        }

    }


    // 실제 주제(concrete subect)
    class CarrierSchedule : AirlineSchedule
    {
        // 제시와 알렉스는 실제 한 곳으로만 비행할 곳을 한다
        public CarrierSchedule(string name, DateTime departing)
            : base(name, "Boston", "Seattle", departing)
        {

        }

    }

    // 관찰자
    interface IATC
    {
        void Update(AirlineSchedule sender, ChangeEventArgs e);
    }
    // 실제 관찰자
    class AirTrafficControl : IATC
    {
        public string Name { get; set; }

        // 생성자
        public AirTrafficControl(string name)
        {
            this.Name = name;
        }
        public void Update(AirlineSchedule sender, ChangeEventArgs e)
        {
            Console.WriteLine("{0} Air Traffic Control Notified :\n {1}'s flight 497 from {2} " +
                "to {3} new departure time : {4:hh:mmtt}", Name, e.ArrivalAirport, e.DepartureAirport, e.ArrivalAirport, e.DepartureDateTime);
            Console.WriteLine("------------------");
        }
        public CarrierSchedule CarrierSchedule { get; set; }
    }

}

 




- 참고  Programming .Net 3.5 by Jesse Liberty and Alex Horovitz.