[디자인 패턴] 옵저버패턴

변진상·2024년 1월 18일
0

학습 기록

목록 보기
7/31

이 글은 면접을 위한 CS 전공지식노트의 책을 읽고 학습 후 스터디 공유를 위한 글입니다.

옵저버 패턴

옵저버 패턴이란?

옵저버 패턴은 주체가 어떤 객체의 상태 변화를 관찰 → 변화가 발생할 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 패턴

용어 정리

  • 주체: 객체의 상태 변화를 보고 있는 관찰자
  • 옵저버: 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 추가 변화 사항이 생기는 객체

객체와 주체가 분리되어있는 옵저버 패턴

객체와 주체가 합쳐진 옵저버 패턴

구현 코드1

interface Subject {
  register(obj: Observer): void;
  notifyObservers(): void;
  getUpdate(): Object;
}

interface Observer {
  update(): void;
}

class Topic implements Subject {
	// Topic이 주체이자 객체.
  private observers: Array<Observer>;
  private message: String;

  constructor() {
    this.observers = new Array<Observer>();
    this.message = "";
  }

  public register(obj: Observer): void {
	// 이 객체+주체에 옵저버를 등록
    if (!this.observers.includes(obj)) this.observers.push(obj);
  }

  public notifyObservers(): void {
		// 구독되어있는 옵저버들의 상태변화 시 실행할 메서드를 실행
    this.observers.forEach((ele) => ele.update());
  }

  public getUpdate(): Object {
    return this.message;
  }

  public updateMessage(msg: String): void{
			// 이 부분이 주체의 기능에 해당하는 부분
			// topic 객체의 상태인 message 프로퍼티를 변경 -> 옵저버에게 이를 알림 
    console.log("Message sended to Topic: " + msg);
    this.message = msg;
    this.notifyObservers();
  }
}

class TopicSubscriber implements Observer {
  private name: String;
  private topic: Subject;

  constructor(name: String, topic: Subject) {
    this.name = name;
    this.topic = topic;
  }

  public update(): void {
		// 이후 주체에 의해 실행 됨
    let msg = this.topic.getUpdate();
    console.log(this.name,": got message >>", msg);
  }
}

const topic = new Topic();

const observerA = new TopicSubscriber('observerA', topic);
const observerB = new TopicSubscriber('observerB', topic);
const observerC = new TopicSubscriber('observerC', topic);

topic.register(observerA);
topic.register(observerB);
topic.register(observerC);

topic.updateMessage("나는 수달이다.")

구현 코드 2(Javascript Proxy 객체를 이용)

💡 프록시란? 프록시 객체는 어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수호출 등)의 작업을 가로챌 수 있는 객체로 다음 두 매개변수를 가진다.
  • target: 프록시할 대상
  • handler: 프록시 객체의 target 동작을 가로채서 정의할 동작들이 구현된 함수
const handler = {
  get: function (target, name) {
    return name === "name" ? `${target.a} ${target.b}` : target[name];
   }
}
 
const p = new Proxy({ a: "A", b: "is not B" }, handler);

console.log(p.name);
// A is not B
  • 프록시로 선언한 객체의 a와 b라는 속성에 특정 문자열을 담아서 handler에 “name”이라는 속성에 접근할 때 a와 b라는 것을 합쳐서 문자열을 리턴하는 동작을 구현
  • p라는 변수에 name이라는 속성을 선언하지 않았는데도 p.name으로 name 속성에 접근하려고 할 때, 그 부분을 가로채 문자열을 만들어 반환

프록시 객체를 이용한 옵저버 패턴

function createReactiveObject(target, callback) { 
    const proxy = new Proxy(target, {
        set(obj, prop, value){
            if(value !== obj[prop]){ // 상태가 변경되었음을 감지하는 부분
                const prev = obj[prop]
                obj[prop] = value 
                callback(`${prop}가 [${prev}] >> [${value}] 로 변경되었습니다`)
            }
            return true
        }
    })
    return proxy 
} 
const a = {
    "동물" : "돼지"
} 
const b = createReactiveObject(a, console.log)
// 동작을 감지(이 경우 동물이라는 프로퍼티의 상태변경) 후 출력
b.동물 = "돼지"
b.동물 = "수달"
// 동물 [돼지] >> [수달] 로 변경되었습니다
  • 프록시 객체의 constructor 두번째 패러미터로 객체가 주어지는데 그 객체 안에서 get, set, has 메서드에 대한 정의를 할 수 있다. 위 코드는 set 함수를 통해 속성에 대한 접근을 가로채 동물이라는 속성의 값이 변경되는 것을 감시할 수 있다.
    • get(): 속성과 함수에 대한 접근을 가로챔
    • set(): 속성에 대한 접근을 가로챔
    • has(): in 연산자의 사용을 가로챔

옵저버 패턴이 사용되고 있는 예

  • 이벤트 기반 시스템과 MVC(Model-View-Contrller) 패턴에도 사용된다.

ex. 주체라고 볼 수 있는 모델에서 변경사항이 생겨 update 메서드로 옵저버인 뷰에 알리고 이를 기반으로 컨트롤러 등이 작동

  • View.js에서도 프록시 객체를 통해 옵저버패턴을 이용.

옵저버 패턴의 장점

  • 느슨한 결합 (Loose Coupling): 주체(Subject)와 옵저버(Observer)는 서로 독립적으로 존재하며, 상호작용할 때 느슨한 결합을 가집니다.
  • 이벤트 기반 시스템 구현: 옵저버 패턴은 이벤트 기반 시스템을 구현하는데 효과적이다. 어떤 객체에서 일어나는 이벤트에 대한 처리를 다수의 객체에서 동시에 수행할 수 있다.
  • 유연성 및 확장성: 새로운 옵저버를 추가하거나 기존 옵저버를 수정하지 않고도 주체 객체에 쉽게 추가할 수 있습니다. 이는 시스템의 유연성과 확장성을 높여줍니다.
  • 분산 이벤트 처리: 분산 환경에서 발생하는 이벤트들을 효과적으로 처리할 수 있다. 네트워크 통신에서도 활용이 가능하며, 이는 다양한 애플리케이션에서 활용될 수 있다.

옵저버 패턴의 단점

  • 순서 보장의 어려움: 옵저버들 간에 호출 순서를 보장하기 어려울 수 있다. 이는 프로그램의 복잡성을 증가시킬 수 있고, 종종 예측하지 못한 동작을 초래할 수 있다.
  • 성능 이슈: 옵저버 패턴을 적용하면서 발생하는 이벤트 처리에 대한 오버헤드가 있을 수 있다. 특히 옵저버가 많고, 이벤트의 발생 빈도가 높은 경우 성능 이슈가 발생할 수 있다.
  • 디버깅의 어려움: 여러 객체 간의 상호작용이 늘어나면 디버깅이 어려워질 수 있다. 특히 복잡한 시스템에서 각각의 옵저버 동작을 파악하기 어려울 수 있다.
profile
자신을 개발하는 개발자!

0개의 댓글