7. Observer Pattern ( 옵져버 패턴 C# )

|

[읽기전에]

  UML 다이어그램 : UML클래스 다이어그램 기본상식 http://hongjinhyeon.tistory.com/25 

  포스팅되는 디자인 패턴의 예는 스타크래프트를 기본으로 하였습니다 : 디자인 패턴을 시작하며 http://hongjinhyeon.tistory.com/24



<기본 정의 >


1. Observer Pattern 정의

 

 -한 객체의 상태가 바뀌면 그 책체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로

  일대다(one-to-many) 의존성을 정의한다.

  (Define a one-to-many dependency between objects so that when one object changes state,

   all its dependents are notified and updated automatically.)


 -한 객체의 상태가 변경되면 그 객체에 의존하는 모든 객체에 연락을 한다.

 


2. UML Diagram


 



3. 사용용도 및 장점


 -옵져버 패턴은 MVC 모델에서 View단( 사용자에게 보여주는 단계)에서 많이 나타날수가 있다, 객체의  상태에대한

   참조를 여러군데에서 하고 있을시에 ( 특히 여러개의 창에서 참조가 될시에 ) 사용될 수 있다.


 -객체의 상태를 참조하는 대상에 일관성을 보장한다.


 -객체의 상태가 변경될 시에 참조를 하고있는 대상들은 자동으로 상태가 업데이트가 된다.



4. 소스


using System;
using System.Collections.Generic;
 
namespace DoFactory.GangOfFour.Observer.Structural
{
  /// <summary>
  /// MainApp startup class for Structural
  /// Observer Design Pattern.
  /// </summary>
  class MainApp
  {
    /// <summary>
    /// Entry point into console application.
    /// </summary>
    static void Main()
    {
      // Configure Observer pattern
      ConcreteSubject s = new ConcreteSubject();
 
      s.Attach(new ConcreteObserver(s, "X"));
      s.Attach(new ConcreteObserver(s, "Y"));
      s.Attach(new ConcreteObserver(s, "Z"));
 
      // Change subject and notify observers
      s.SubjectState = "ABC";
      s.Notify();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// <summary>
  /// The 'Subject' abstract class
  /// </summary>
  abstract class Subject
  {
    private List<Observer> _observers = new List<Observer>();
 
    public void Attach(Observer observer)
    {
      _observers.Add(observer);
    }
 
    public void Detach(Observer observer)
    {
      _observers.Remove(observer);
    }
 
    public void Notify()
    {
      foreach (Observer o in _observers)
      {
        o.Update();
      }
    }
  }
 
  /// <summary>
  /// The 'ConcreteSubject' class
  /// </summary>
  class ConcreteSubject : Subject
  {
    private string _subjectState;
 
    // Gets or sets subject state
    public string SubjectState
    {
      get { return _subjectState; }
      set { _subjectState = value; }
    }
  }
 
  /// <summary>
  /// The 'Observer' abstract class
  /// </summary>
  abstract class Observer
  {
    public abstract void Update();
  }
 
  /// <summary>
  /// The 'ConcreteObserver' class
  /// </summary>
  class ConcreteObserver : Observer
  {
    private string _name;
    private string _observerState;
    private ConcreteSubject _subject;
 
    // Constructor
    public ConcreteObserver(
      ConcreteSubject subject, string name)
    {
      this._subject = subject;
      this._name = name;
    }
 
    public override void Update()
    {
      _observerState = _subject.SubjectState;
      Console.WriteLine("Observer {0}'s new state is {1}",
        _name, _observerState);
    }
 
    // Gets or sets subject
    public ConcreteSubject Subject
    {
      get { return _subject; }
      set { _subject = value; }
    }
  }
}


 


5. 실행결과


Observer X's new state is ABC

Observer Y's new state is ABC

Observer Z's new state is ABC




< 실제 적용 >


1. UML Diagram






2. 사용용도 및 장점


 -옵저버의 패턴은 스타크래프트에서 필수적인 요소입니다. 사용되는 곳은 여러곳이 있는데 여기서 예를 드는것은 유닛의 상태입니다.

  마린의 체력 상태를 모니터링하는 곳은 메인화면( 케릭터의 에너지바가 칸수로 나옴 ), 상태창(캐릭터의상태가 숫자 표시 및 색으로 표현),

  적의 화면 ( 상대편의 화면 및 케릭터 상태창에서의 체력 표시 )이 있습니다. 


 -아군 마린이 적의 마린의 공격으로 체력이 깍였다면 위의 3군데 모두 동일하게 남은 체력이 보여야 할 것입니다. 이럴때에 옵져버 패턴을

  사용하면 동일한 시점에 동일한 상태를 나타내게 됩니다.


   


3. 소스


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

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

            Marine ourMarine = new Marine("아군 마린", 100);
            ourMarine.Attach(new MainScreen());
            ourMarine.Attach(new StatusScreen());
            ourMarine.Attach(new EnemyScreen());

            ourMarine.Health = 60;
            ourMarine.Health = 40;
          
            Console.ReadKey();
        }
       
        abstract class Unit
        {
            private string name;
            private int health;
            private List<UnitViewer> unitViewers = new List<UnitViewer>();

            public Unit(string name, int health)
            {
                this.name = name;
                this.health = health;
            }

            public void Attach(UnitViewer investor)
            {
                unitViewers.Add(investor);
            }

            public void Detach(UnitViewer investor)
            {
                unitViewers.Remove(investor);
            }

            public void Notify()
            {
                foreach (UnitViewer unitviewr in unitViewers)
                {
                    unitviewr.Update(this);
                }
            }

          
            public int Health
            {
                get { return health; }
                set
                {
                    health = value;
                    Notify();
                }
            }
          
            public string Name
            {
                get { return name; }
            }
        }

        class Marine : Unit
        {
            public Marine(string name, int health)
                : base(name, health)
            {
            }
        }

        
        interface UnitViewer
        {
            void Update(Unit unit);
        }


        class MainScreen : UnitViewer
        {
            private Unit unit;

            public void Update(Unit _unit)
            {
                this.unit = _unit;
                Console.WriteLine("메인화면 {0} 상태 변경 : 체력 {1}", this.unit.Name, this.unit.Health.ToString());
            }
            
            public Unit Unit
            {
                get { return unit; }
                set { unit = value; }
            }
        }

        class StatusScreen : UnitViewer
        {
            private Unit unit;

            public void Update(Unit _unit)
            {
                this.unit = _unit;
                Console.WriteLine("상태창 {0} 상태 변경 : 체력 {1}", this.unit.Name, this.unit.Health.ToString());
            }

            public Unit Unit
            {
                get { return unit; }
                set { unit = value; }
            }
        }

        class EnemyScreen : UnitViewer
        {
            private Unit unit;

            public void Update(Unit _unit)
            {
                this.unit = _unit;
                Console.WriteLine("적 상태창 {0} 상태 변경 : 체력 {1}", this.unit.Name, this.unit.Health.ToString());
            }

            public Unit Unit
            {
                get { return unit; }
                set { unit = value; }
            }
        }
    }
}

-UnitViewer를 인터페이스로 선언한 것은 참조되는 클래스들이 연관성이 많이 없을 때 유용하다. 

 굳이 UnitViewer를 상속해야하는 제한에서 벗어나서 마린의 객체를 참조하고자 하는 클래스들은 옵저버 인터페이스를 구현만 하면

 상속받은 객체가 아니라도 마린의 상태를 볼 수 있게된다. (즉 더 유연한 구현을 할수가 있게된다.)



4. 실행결과





5. 피드백


  -옵져버 패턴은 현업에서도 자주 사용될 수있는 패턴인듯하다. 특히 사용자에게 자료를 여러군데에서 보여줄때, 자료가 수정되면

   열려진 창에서 모두데이터가 수정되게 하는 부분에서 유용할 듯 하다.




[참조]

 -http://www.dofactory.com

 -Head First Design Patterns

And