본문 바로가기

디자인 패턴 ( Design Pattern )

[디자인 패턴] 1. 옵서버 패턴( Observer Pattern )

음... 뭔가 순서가 뒤죽박죽 될거 같은 느낌... +_+;;

회사에서 진행하는 스터디로 인해서 일단 옵서버 패턴과과 데커레이터 패턴( Decorator Pattern ) 에 대해서 작성하고 나중에 차근차근 하나씩 업데이트 시켜야 겠다. 일단 오늘은 옵서버 패턴에 대해서 공부해보자 





Definition

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

 즉, 옵서버 패턴( Observer Pattern ) 은 데이터의 변경이 발생했을 경우 상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 유용하다. 예를 들어 새로운 파일이 추가되거나 기존 파일이 삭제되었을 때 탐색기는 이를 즉시 표시할 필요가 있다. 탐색기를 복수 개 실행하는 상황이나 하나의 탐색기에서 파일 시스템을 변경했을 때는 다른 탐색기에게 즉각적으로 이 변경을 통보해야 한다.



UML Class Diagram



The classes and objects participating in this pattern are:

  • Subject  (Stock)
    • knows its observers. Any number of Observer objects may observe a subject
    • provides an interface for attaching and detaching Observer objects.
  • ConcreteSubject  (IBM)
    • stores state of interest to ConcreteObserver
    • sends a notification to its observers when its state changes
  • Observer  (IInvestor)
    • defines an updating interface for objects that should be notified of changes in a subject.
  • ConcreteObserver  (Investor)
    • maintains a reference to a ConcreteSubject object
    • stores state that should stay consistent with the subject's
    • implements the Observer updating interface to keep its state consistent with the subject's


여기에 초록색으로 Stock, IBM, Investor 라고 쓰여져 있는건 이따 실제 코드에서 사용되는 사례를 보여주기 위해 쓰여졋음을 미리 알려드리고... +_+ ㅋㅋ Subject 는 Observer 들이 등록되는 객체이고, 그들에게 변화를 알려주는 주체라 보면 된다. ConcreteSubject 는 위에서 말한 Subject 들이 실제 구현된 객체라 보면 된다. 


음.... 나도 사실 용어가 헷갈려서 첨 읽었을땐 뭔말이야?? ㅡㅡ 라고 많이 생각했지만 아래 실사례를 보면 어느정도 이해가 된다. 그런데 그 전에 간략하게 요약을 하자면 Observer 는 어떤 변화를 감지하기 위한 객체이다. 위의 초록색 글씨에서 보면 Investor 들은 IBM 의 Stock 이 변화됨에 따라 그 변화를 감지하고 어떤 액션을 취해야 할것이다. 


 또 다른 예를 들어보자면 Angular 가 React 와 같은 UI Componet 의 예를 들어보자. ( 물론 실제로 이것은 내부적으로 옵저버 패턴으로 구현되어 있다! 라고 말하긴 힘들지만 예시를 들어 설명하기 위함이니 이 부분은 양해 바란다. ) 우리가 화면을 작성하기 위해 HTML 코드를 작성하고, 이것에 대한 Domain 객체를 생성해보자. Domain 객체의 변수가 바뀌면 자동적으로 화면의 값 또한 바뀌어야 한다고 하면 화면의 객체들이 Domain 객체의 변화를 감지하고 있다가, Domain 객체의 값이 바뀌면 그 변화를 감지해서 화면에 반영해주면 되지 않을까?? ^^


이런 용도를 설명하면 Observer 패턴을 언제 쓰면 좋을지를 고민해 볼수 있을것 같다.


Structural Code with C#

using System;
using System.Collections.Generic;
 
namespace DoFactory.GangOfFour.Observer.Structural
{
  /// 
  /// MainApp startup class for Structural 
  /// Observer Design Pattern.
  /// 
  class MainApp
  {
    /// 
    /// Entry point into console application.
    /// 
    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();
    }
  }
 
  /// 
  /// The 'Subject' abstract class
  /// 
  abstract class Subject
  {
    private List _observers = new List();
 
    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();
      }
    }
  }
 
  /// 
  /// The 'ConcreteSubject' class
  /// 
  class ConcreteSubject : Subject
  {
    private string _subjectState;
 
    // Gets or sets subject state
    public string SubjectState
    {
      get { return _subjectState; }
      set { _subjectState = value; }
    }
  }
 
  /// 
  /// The 'Observer' abstract class
  /// 
  abstract class Observer
  {
    public abstract void Update();
  }
 
  /// 
  /// The 'ConcreteObserver' class
  /// 
  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; }
    }
  }
}




Real-world code with C#


code

using System;
using System.Collections.Generic;
 
namespace DoFactory.GangOfFour.Observer.RealWorld
{
  /// 
  /// MainApp startup class for Real-World 
  /// Observer Design Pattern.
  /// 
  class MainApp
  {
    /// 
    /// Entry point into console application.
    /// 
    static void Main()
    {
      // Create IBM stock and attach investors
      IBM ibm = new IBM("IBM", 120.00);
      ibm.Attach(new Investor("Sorros"));
      ibm.Attach(new Investor("Berkshire"));
 
      // Fluctuating prices will notify investors
      ibm.Price = 120.10;
      ibm.Price = 121.00;
      ibm.Price = 120.50;
      ibm.Price = 120.75;
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// 
  /// The 'Subject' abstract class
  /// 
  abstract class Stock
  {
    private string _symbol;
    private double _price;
    private List _investors = new List();
 
    // Constructor
    public Stock(string symbol, double price)
    {
      this._symbol = symbol;
      this._price = price;
    }
 
    public void Attach(IInvestor investor)
    {
      _investors.Add(investor);
    }
 
    public void Detach(IInvestor investor)
    {
      _investors.Remove(investor);
    }
 
    public void Notify()
    {
      foreach (IInvestor investor in _investors)
      {
        investor.Update(this);
      }
 
      Console.WriteLine("");
    }
 
    // Gets or sets the price
    public double Price
    {
      get { return _price; }
      set
      {
        if (_price != value)
        {
          _price = value;
          Notify();
        }
      }
    }
 
    // Gets the symbol
    public string Symbol
    {
      get { return _symbol; }
    }
  }
 
  /// 
  /// The 'ConcreteSubject' class
  /// 
  class IBM : Stock
  {
    // Constructor
    public IBM(string symbol, double price)
      : base(symbol, price)
    {
    }
  }
 
  /// 
  /// The 'Observer' interface
  /// 
  interface IInvestor
  {
    void Update(Stock stock);
  }
 
  /// 
  /// The 'ConcreteObserver' class
  /// 
  class Investor : IInvestor
  {
    private string _name;
    private Stock _stock;
 
    // Constructor
    public Investor(string name)
    {
      this._name = name;
    }
 
    public void Update(Stock stock)
    {
      Console.WriteLine("Notified {0} of {1}'s " +
        "change to {2:C}", _name, stock.Symbol, stock.Price);
    }
 
    // Gets or sets the stock
    public Stock Stock
    {
      get { return _stock; }
      set { _stock = value; }
    }
  }
}


위 코드에서 핵심은 Stock 의 Price 가 변경될 때 (Price 의 Setter 가 호출될 때, 그 변화를 감지하고 Notify() 메서드를 호출해서 Observer 들에게 변화가 되었음을 알리는 것이다!! )

output

Notified Sorros of IBM's change to $120.10
Notified Berkshire of IBM's change to $120.10

Notified Sorros of IBM's change to $121.00
Notified Berkshire of IBM's change to $121.00

Notified Sorros of IBM's change to $120.50
Notified Berkshire of IBM's change to $120.50

Notified Sorros of IBM's change to $120.75
Notified Berkshire of IBM's change to $120.75