싱글톤 패턴, pub-sub/observer 패턴, EventEmitter

HongBoogie·2024년 8월 8일

싱글톤 패턴이란?

싱글톤 패턴은 특정 인스턴스가 오직 하나만 존재하도록 보장하는 소프트웨어 설계 패턴이다. 전역 변수를 사용하지 않고도 해당 객체를 전역적으로 접근 할 수 있게 되며 공유 자원에 대한 동시 접근을 제어할 수 있다.

보통 싱글톤 패턴에 대해 설명할때 클래스를 기반으로 설명하는데 자바스크립트를 사용하는 이상 굳이 클래스를 쓰지 않아도 된다. 이는 자바스크립트가 프로토타입 기반의 언어이기 때문. 자바스크립트의 일반적인 함수와 객체, 클로저를 사용해서 구현이 가능하다.

//Singleton.js

let instance;

export class Singleton {
	constructor(data) {
		if (instance)
			return instance
		this.data = data;
		instance = this;
	}
	getData() {
		return this.data;
	}
	setData(data) {
		this.data = data;
	}
}	

간단한 예시인데, 생성자를 통해 인스턴스를 만들면, 다음에 새로운 생성자를 만들더라도 전역변수로 저장된 instance를 리턴해준다.

즉 게터와 세터로 이미 만들어진 인스턴스를 업데이트 하는 것은 가능하지만, 새로운 인스턴스를 만드는 것은 불가능하다는 뜻이다.

이는 class 없이 함수 , 프로토타입으로도 구현이 가능하다.

Pub-Sub 패턴이란?

발행 - 구독 모델은 비동기 메세지 패러다임으로, publisher와 subscriber 사이에 message broker를 둠으로써 pub와 sub가 서로를 전혀 몰라도 상관 없도록 구현하는 디자인 패턴이다.

Untitled

패턴 절차를 설명해보자면

  1. 이벤트(메시지)를 발행하는 Publisher가 존재하며, Publisher는 특정 Channel(혹은 Topic)에 이벤트 전송한다.
  2. Publisher는 Subscriber를 모른체로 이벤트 발생 시 Event Channel에게 메세지를 넘겨준다.
  3. Subscriber는 Channel(혹은 Topic)을 구독하게 된다.
  4. 중간 컴포넌트는 이벤트들을 필터링해서 받아야 할 수신자들에게 보내준다.

즉, subscriber는 publisher에 대한 정보 없이 channel에 할당된 작업만 모니터링하다 해당 이벤트에 맞는 메시지를 받으면 작업을 시작하는 것이다.

장점

  • scalable하다
  • Observer 패턴에 비해 결합도가 낮다.

단점

  • 일관성이 없다.
  • 메시지 관리가 어려울 수 있다.

Observer패턴이란

  • 감시자 패턴이라고도 부르며, 옵저버와 관찰 대상 객체로 구성되어 있다.
  • 클라이언트 측 자바스크립트 프로그래밍에서 널리 사용되는 패턴이다.

!https://velog.velcdn.com/images/onedanbee/post/d9c067eb-b387-4101-8e98-4d9321b0ded8/image.png

1. 옵저버(관찰자)

  • 객체의 변화를 감지할 감시자이다.
  • 객체의 상태 변화가 있을 때 동작할 메소드를 가진다. (ex. update())
  • 여러 옵저버가 필요할 시, 옵저버 클래스를 만들고 이 클래스를 상속해 구현 가능.

2. 객체(관찰대상)

  • 객체는 해당 객체의 변화를 감시하는 옵저버 리스트를 저장하고 있다.
  • subscribe()를 사용해 옵저버를 추가한다.
  • 객체 상태가 변하면(ex. 상태값 변경, 이벤트 발생) notify()를 사용해 옵저버에게 알려준다.
  • 옵저버는 notify()를 통해 객체 상태 변화를 감지하고, update()를 실행한다.

옵저버 패턴의 작동 방식은 간단한데, 모듈들에게 옵저버를 등록하여 상태 변화가 있을 때마다 메서드를 통해 옵저버에게 통지하도록 하는 시스템이다. 모듈들은 상태 변화가 일어났을 대 관찰자(observer)에게 필요한 데이터와 함께 자신의 상태 변화를 통보한다.

Observer패턴 장단점

장점

  • Subject의 상태 변경을 주기적으로 조회하지 않고 자동으로 감지할 수 있다.
  • 발행자의 코드를 변경하지 않고도 새 구독자 클래스를 도입할 수 있어 개방 폐쇄 원칙을 준수한다.
    • 개방 폐쇄의 원칙(OCP)이란 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙을 말한다.
  • 상태를 변경하는 객체(Subject)와 변경을 감지하는 객체(Observer)의 관계를 느슨하게 유지할 수 있다. (느슨한 결합)

단점

  • 구독자는 알림 순서를 제어할수 없고, 무작위 순서로 알림을 받음(하드 코딩으로 구현할수는 있겠지만, 복잡성과 결합성만 높아지기 때문에 추천되지는 않는 방법이다.)
  • 옵저버 패턴을 자주 구성하면 구조와 동작을 알아보기 힘들어져 코드 복잡도가 증가한다.

Event Emitter

Node..js에서 이벤트 기반 프로그래밍을 할때 사용되는 클래스이다. Node.jsdㅔ서 가장 기본적인 이벤트 처리 방식 중 하나이다.

EventEmitter 클래스를 상속한 객체를 만들고, on() 메서드를 사용해 이벤트 리스너를 등록하여 이벤트가 발생할 때마다 등록된 콜백 함수가 실행된다.

이벤트는 문자열 형태의 이름과 함께 발생하며, 이벤트에 대한 데이터를 선택적으로 전달할 수도 있다.

const eventEmitter = require('events')

class CustomEventEmitter extends EvemtEmitter {}

const customEmitter = new CustomEventEmitter();

customEmitter.on('customEvent' , (arg1, arg2) => {
	console.log('이벤트가 발생했습니다.', arg1, arg2);
	});
	
customEmitter.emit('customEvent', 'hello', 'world');

위 코드는 CustomEventEmitter 클래스를 만들고, on() 메서드를 통해 customEvent를 처리하는 이벤트를 발생시킨다. emit() 메서드의 두 번째 인수 이후의 인수들은 이벤트 리스너에 전달되는 인수이다.

Promise와 EventEmitter의 차이

Promise

Promise는 비동기 작업을 수행하는 함수가 반환하는 객체로, 성공하면 결과 값을 반환하고 실패하면 에러를 반환한다. 이러한 특성으로 Promise는 비동기 작업의 성공 또는 실패 상태를 쉽게 확인하고 처리할 수 있다.

EventEmitter

EventEmitter는 위와 달리 이벤트 기반 프로그래밍에서 사용되는 객체로, 이벤트를 발생시키고, 이벤트가 발생했을 때 처리할 콜백 함수를 등록한다. 이벤트는 이름과 함께 발생하며, 발생한 이벤트를 처리할 수 있는 모든 리스너에게 알린다. 이를 통해 여러 개의 콜백 함수를 동시에 처리가 가능하다.

EventEmitter Method

on

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => {
  console.log('Success!');
});

myEmitter.emit('test');

on 메소드는 이벤트 핸들러를 설정하는 메소드다. 참고로, on 메소드와 같은 용도를 가진 addListener라는 메소드도 있다.

https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=3759&directory=Untitled.png&name=Untitled.png

보통 길이가 더 짧은 on 메소드를 쓰지만 다른 개발자가 addListener라고 쓴 코드를 봐도 바로 이해할 수 있도록 기억해두자.

emit

emit 메소드는 인위적으로 이벤트를 발생시키기 위해 쓰는 메소드다.

once

once 메소드는 특정 이벤트에 대한 이벤트 핸들러를 등록한다는 점에서 on 메소드와 유사한 메소드. 하지만 한 가지 특이한 점이 있는데, 그건 바로 그 이벤트 핸들러가 해당 이벤트에 대해서 딱 한 번만 반응해서 실행되도록 한다는 점이다.

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.once('test', () => {
  console.log('Success!');
});

myEmitter.emit('test');
myEmitter.emit('test');
myEmitter.emit('test');

on 메소드를 once 메소드로 바꿨다. 그리고 test 이벤트를 총 3번 발생시켜(emit)줬는데

https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=3759&directory=Untitled%201.png&name=Untitled+1.png

'Success!'가 딱 한 번만 출력된 것을 알 수 있다. once 메소드로 등록된 이벤트 핸들러는 한번 실행된 후에는 삭제된다고 생각하면 된다. 특정 이벤트가 어차피 한 번밖에 발생하지 않는 경우나, 이벤트 핸들러가 딱 한 번만 실행되기를 바라는 경우 이 once 메소드를 사용하면 된다.

listeners

특정 이벤트에 대한 이벤트 핸들러들을 출력해주는 메소드이다.

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.once('test', () => {
  console.log('A');
});

myEmitter.once('test', () => {
  console.log('B');
});

myEmitter.once('test', () => {
  console.log('C');
});

console.log(myEmitter.listeners('test'));

위 코드는 test 이벤트에 총 3개의 이벤트 핸들러가 설정된 상황이다.

https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=3759&directory=Untitled%202.png&name=Untitled+2.png

listeners를 찍어보면 이렇게 총 3개의 이벤트 핸들러(함수, Function)들이 설정돼있다는 걸 알 수 있다.

5. off 메소드

이벤트 핸들러를 설정하는 메소드가 있으면, 이벤트 핸들러를 해제하는 메소드도 있어야겠죠? 해제하는 메소드는 바로 off 메소드입니다. 설정은 on, 해제는 off, 외우기 쉽죠?

off 메소드는 이벤트 핸들러를 해제하는 메소드이다. off는 해제할 이벤트 핸들러를 정확히 지정해줘야 한다.

const EventEmitter = require('events');

const myEmitter = new EventEmitter();

myEmitter.on('test', () => { // --- (A)
  console.log('Success!');
});

myEmitter.off('test', () => { // --- (B)
  console.log('Success!');
});

myEmitter.emit('test');

https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=3759&directory=Untitled%203.png&name=Untitled+3.png

off 메소드를 썼는데도 이렇게 Success가 출력된 이유는 (A) 이벤트 핸들러와 (B) 이벤트 핸들러는 똑같이 생겼지만 서로 다른 함수이기 때문이다.

profile
개발이 즐겁고 노는게 즐거워요

0개의 댓글