디자인 패턴 - Observer pattern

김정우·2023년 1월 31일
0

design-pattern

목록 보기
1/1

개발을 하다보면 어떤 기능을 구현하기 위한 여러가지 방법이 떠오를 때가 있다.
그러면 보통 유지보수 및 확장성, 로직의 명료함 등등을 고려하여 그 중 하나로 선택 후 구현을 하는데 아직까지는 스스로의 선택에 100% 확신을 갖지는 못하고 있다.

개발 중 이러한 작업을 마치고 나면 자연스럽게 디자인 패턴에 대한 관심으로 이어진다.
오늘은 그 시작으로 Observer 패턴에 대해 공부해보았다.

behavioral patterns (행위 패턴)

행위 패턴이란 객체가 데이터를 공유하는 방법이나 객체 사이에서 데이터를 교환하는 방법에 대한 가이드를 제공한다. 데이터나 태스크의 책임을 분배하면서도 각 책임간의 결합도를 최소화 하려는 목적이 있는 패턴이라고 한다.

Observer pattern (옵저버 패턴)

옵저버 패턴은 말그래도 어떠한 객체의 상태를 관찰하다가 상태 변화가 감지되었을 때 해당 사실을 관찰자들에게 전파하는 패턴이다.

즉, 관찰자(observer)와 관찰 대상 객체간의 관계를 구조화하는 것이다. 옵저버 패턴에서 사용하는 주요한 개념을 먼저 짚고 넘어가보자.

  1. observer
    관찰자는 특정 객체의 상태 변화를 기다렸다가, 상태 변화가 발생했을 때 특정 행동을 수행하는 일종의 '구독자'이다.
  2. observable
    observable은 옵저버가 구독하고 있는 대상으로서 다음과 같은 기능을 수행할 수 있다.
    • subscribe: 특정 옵저버를 observable 객체에 구독시킨다.
    • notify: 현재 객체를 구독하고 있는 옵저버들에게 상태의 변경을 전달한다.

간단한 상황을 전제하고 패턴을 사용해서 코드를 짜보자.

파스타, 스테이크 세션으로 나누어 조리를 하는 레스토랑에서 주문이 들어올 때마다 각 세션에서 조리해야 할 메뉴를 알고 싶다고 가정한다.

먼저 observer 객체는 다음과 같이 구성된다.


class SteakSession {
  constructor() {
    this.menu = null;
  }

  cook(order) {
    this.menu = order.steak;
    console.log(`스테이크 세션: ${order.steak} 조리 시작합니다.`);
  }
}

class PastaSession {
  constructor() {
    this.menu = null;
  }

  cook(order) {
    this.menu = order.pasta;
    console.log(`파스타 세션: ${order.pasta} 조리 시작합니다.`);
  }
}

각 세션에서 cook이라는 행동을 수행하면 인자로 넘겨받는 주문 중 자기 세션의 메뉴를 조리한다는 정보를 출력한다.

다음으로 해당 observer들이 구독할 observable 객체는 다음과 같이 만들 수 있다.

class OrderManager {
  constructor () {
    this.sessions = [];
  }

  subscribe(session) {
    this.sessions.push(session);
  }

  takeOrder(order) {
    this.sessions.forEach(session => session.cook(order));
  }
}

내부적으로 구독하는 옵저버들의 리스트를 관리하고, 구독 중인 옵저버들에게 데이터의 변화를 알릴 수 있는 메서드를 가지고 있다.
(전제된 상황에 맞추어 observers -> sesions, notify -> takeOrder 로 이름 변경)

const orderManager = new OrderManager();

orderManager.subscribe(new SteakSession());
orderManager.subscribe(new PastaSession());

orderManager.takeOrder({
  pasta: '봉골레',
  steak: '티본',
});

만약 누군가 새로운 주문을 하게 된다면(상태 변화), 구독된 각 세션에서는 주문 중 자기 세션의 메뉴를 조리하기 시작한다(상태 변화를 감지 후 행동 수행).

스테이크 세션: 티본 조리 시작합니다.
파스타 세션: 봉골레 조리 시작합니다.

예시 코드로 옵저버 패턴의 사용 방식을 확인해보았으니 장단점을 다음과 같이 정리할 수 있을 것 같다.

  1. 장점
    • 관심사와 책임의 분리
      observer 객체와 observable 객체는 강하게 결합되어 있지 않아 언제든지 분리될 수 있어 데이터의 변경와 데이터를 처리하는 로직, 로직을 관리하는 책임을 유연하게 관리할 수 있다.
  2. 단점
    • 메모리 누수 우려
      자바스크립트에서는 가비지 컬렉터를 통해 메모리를 관리한다. 따라서 옵저버가 더이상 특정 객체를 구독할 필요가 없음에도 구독이 해지되지 않는다면, 계속 참조(구독)되고 있기 때문에 해당 옵저버의 메모리가 해제되지 않는다는 점을 조심해야 한다.


추가로 궁금한 점

이전까지 디자인 패턴의 학습을 미루어왔던 가장 큰 이유는 어떠한 패턴을 학습한 것을 실제 나의 개발에 어떻게 반영할 지, 그리고 내가 사용하고 있는 기능에 어떻게 반영되어 있는지 감이 잘 오지 않았기 때문이다.

이런 학습은 패턴을 익힌다고 하더라도 실제로 내가 패턴을 활용하는데에는 큰 도움을 주지 못할 것 같아서, 이어지는 다음 글에서는 옵저버 패턴이 (1) 내 개발에 어떻게 적용될 수 있을지와 (2) 내가 사용하는 기능에 어떻게 활용되고 있는지 를 보다 깊게 살펴보려고 한다.

대략 글의 소재는 다음과 같다.

  1. 반응형

결국 특정 상태의 변화와 이 변화의 전파에 관한 패턴이라는 점에서 '반응형'이라는 키워드가 떠올랐고, 위키에 반응형 프로그래밍을 검색해본 결과 기본 개념과 맞닿아 있는 것 같았다.

In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. (wikipedia)

그래서 찾아보니 RxJS 자체가 Observer 패턴을 구현한 라이브러리라고 한다. 그동한 반응형 프로그래밍의 얘기는 많이 들어왔었는데, 이전에 반응형 프로그래밍 관련 세미나를 엄청 어렵게 들었던 기억이 있어 학습은 미루고 있었다.

다음 글에서 반응형 프로그래밍이란 어떠한 것인지 대략적인 그림을 파악하고자 한다.


  1. 리액트 훅

반응형에 초점을 두고 조금 더 생각해보니 주로 사용하는 리액트의 useEffect 훅과도 유사한 점이 있는 것 같았다.

const Example = () => {
  	useEffect(() => {
    	grillSteak(order.steak);
      	cookPasta(order.pasta);
    }, [order]);
  
	return <div>example</div>
}

의존성 배열에 포함된 값이 변경될 때마다 (상태 변화), callback 함수가 실행되는 것이 옵저버 패턴에서 구독 후 상태 변화를 전파 받는 것과 유사한 것 같았는데 옵저버 패턴과 어떠한 점이 다른지, 다르다면 왜 옵저버 패턴을 사용하지 않았는 지 등을 학습하려고 한다.

profile
hello world!

0개의 댓글