๐ฅ 28-B ํ, J238 J188
์ฒดํฌํฌ์ธํธ
ํ์ตํ๊ธฐ
-
Publisher-Subscriber ํจํด๊ณผ ์ฑ๊ธํค ํจํด
-
๋๊ธฐ์ ๋น๋๊ธฐ ์์ ์ฒ๋ฆฌ ๋ฐฉ์ ๋น๊ต
-
๋น๋๊ธฐ ํจ์ ํธ์ถ ๊ณผ์
-
๋๊ธฐํ ๊ณผ์
-
๋ฉํฐ ์ค๋ ๋ ๋ฐฉ์๊ณผ ์ค๋ ๋์์ ๋น๋๊ธฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ ๋ฐฉ์
-
ํน์ ์ค๋ ๋์์ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ๋ง๋ค์ด ๊ฐ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ์
-
๋น๋๊ธฐ ์์ ์ ๋๊ธฐํํด์ ๊ธฐ๋ค๋ฆฌ๋ ๋ฐฉ์
-
๋น๋๊ธฐ ์์ ์ ๊ทธ๋ฃน์ผ๋ก ๋ฌถ์ด์ ๋๊ธฐํํ๋ ๋ฐฉ์
-
๋ ธ๋ ํ๊ฒฝ์์ ์ด๋ฒคํธ ๋ฃจํ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ(Event Emmiter) ๋ฐฉ์
-
์ฌ๋ฌ Thread๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค์ด Pool ํํ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ์
์ค๊ณ ๋ฐ ๊ตฌํํ๊ธฐ
-
EvenyManager ํด๋์ค ์ค๊ณ
-
SharedInstance ํจ์
-
Subscriber ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ํ
-
Subscriber ํด๋์ค
-
Sender ํด๋์ค
-
remove ํจ์
-
postEvent ํจ์
-
stringify ํจ์
-
๋น๋๊ธฐ ๊ฐ์ โ 2024-07-26
๊ตฌ์กฐ ์ค๊ณ
classDiagram Subscriber <|-- EventManager EventManager <|-- sharedInstance sharedInstance <|-- Publisher class Publisher{ name: string eventmanager = sharedInstance() +postEventPub() +asyncPostEventPub() +delayPostEventPub() } class Subscriber{ name: string } class EventManager { subscribersInfo: string[] static instance add() remove() postEvent() stringify() checkValidity() addSubscriber() callEvent() asyncPostEvent() } class Event{ +name: string +sender: object +userData: object } class sharedInstance{ return EventManager }
Pub-Sub ํจํด
๋ฐํ-๊ตฌ๋ ํจํด์ Publisher๊ฐ Subscriber์ ์์น๋ ์กด์ฌ๋ฅผ ์ ํ์ ์์ด ๋ธ๋ก์ปค์๊ฒ ๋ฉ์์ง๋ฅผ ๋์ ธ๋๊ธฐ๋ง ํ๋ฉด ๋๋ฉฐ ๋ฐ๋๋ก Subscriber ์ญ์ ๋ธ๋ก์ปค์ ํ ๋น๋ ์์ ๋ง ๋ชจ๋ํฐ๋งํ๋ค ์์ ํ๋ฉด ๋๋ฏ๋ก ์ต์ ๋ฒ ํจํด์ ๋นํด ๊ฒฐํฉ๋๊ฐ ๋ ๋ฎ๋ค.
๋ํ ๋ฐํ-๊ตฌ๋ ํจํด์ ๋ธ๋ก์ปค๋ผ๋ ์ค๊ฐ ๋งค๊ฐ์ฒด๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ธ๋ก์ปค์ ์ง์ ์ ๊ทผํ์ฌ ์ฒ๋ฆฌํ ์๋ ์๋ค.
๊ตฌํํ๊ธฐ
EventManager ํด๋์ค
EventManager ํด๋์ค๋ ์ฑ๊ธํค ํจํด์ ์ด์ฉํ์ฌ ๊ณต์ ํ๋ EventManager๋ฅผ ํตํด Publisher๊ฐ EventManager์ ๊ฐ ์ ์๋ ๊ฒฝ๋ก๋ฅผ ๋ง๋ จํ๋ค๊ณ ์๊ฐํ๋ค. SharedInstance๋ผ๋ ํจ์๋ฅผ ๋ง๋ค์ด new ํค์๋๋ฅผ ํตํ ์ธ์คํด์ค๋ฅผ ์์ฑํ์ง๋ง, ์์ฑ๋ ์ธ์คํด์ค๊ฐ ์์ ๊ฒฝ์ฐ ๊ธฐ์กด์ ์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด Publisher์ eventManager property์๋ ๋ฃ์ด์ฃผ์ด ์ ๊ทผํ ์ ์๊ฒ ํ์๋ค.
class Publisher {
constructor(name) {
this.name = name;
this.eventManager = sharedInstance();
}
...
}class EventManager {
static instance;
constructor() {
if (EventManager.instance) {
return EventManager.instance;
}
this.subscriberInfos = []; // ๊ตฌ๋
์ ๋ชฉ๋ก ์ ์ฅ ๋ฐฐ์ด
EventManager.instance = this;
this.eventEmitter = new EventEmitter();
}
...์ด๋ ๊ฒ Publisher๊ฐ ์ง์ ์ ์ผ๋ก ๊ณต์ ํ๋ ์ธ์คํด์ค๋ฅผ ํตํด Publisher ์์ฒด์ property์์ EventManager๋ก ๊ฐ ์ ์๊ฒ ํด์ค ๋ค์์ ์ด๋ฒคํธ๋ฅผ ์ถ๊ฐํ๋ ๋ฉ์๋๋ฅผ Publisher ์์์ ๋ง๋ ๋ค์ EventMager๋ก ์ ๋ฌํด์ฃผ์ด ์์ฑ๋๊ฒ๋ ํด์ฃผ๋ฉด์ ๋ค์์ ์ด๋ฒคํธ๊ฐ ์๋๋ผ๋ ํ๋์ Publisher๋ฅผ ํตํด ๋ง๋ค ์ ์๋ ๋ฐฉ๋ฒ์ ๊ณ ์ํ๋ค.
add ํจ์
// subscriber ๋ฑ๋ก (eventName, sender)
add(subscriber, eventName, sender, handler) {
const subscribeData = {
subscriber: subscriber,
eventName: eventName,
sender: sender,
handler: handler,
};
if (
this.subscriberInfos.some((subInfo) =>
this.#checkValidity(subInfo, subscribeData)
)
) {
return;
}
this.subscriberInfos.push(subscribeData);
return;
}์ ์ฒด์ ์ผ๋ก EventManager์์ ์กด์ฌํ๋ ๋ฉ์๋๋ค์ ๋ณต์กํ ๋ก์ง์ผ๋ก ๋์ด ์์ง ์๋ค. ์ ํจ์ฑ(๊ฐ์ ์ด๋ฆ์ ์ด๋ฒคํธ ๊ฑธ๋ฌ์ฃผ๊ธฐ)๋ง ๊ฒ์ฌํด์ค ๋ค์ subscriberInfos๋ผ๋ ๋ฐฐ์ด์ ๋ค์ด๊ฐ ์ ์๊ฒ๋ ํ๊ณ , ๋์ค์ ํด๋น ๋ฐฐ์ด์ ๋ํด์๋ง ๊ด๋ฆฌํ๋ฉด์ ์ด๋ฒคํธ์ ๋ํด ์ฒ๋ฆฌํ ์ ์๋๋ก ํด์ฃผ์๋ค.
PostEvent ํจ์
postEvent(eventName, sender, userInfo) {
const newEvent = new Event(eventName, sender, userInfo);
let result;
this.subscriberInfos.forEach((subscriberInfo) => {
if (
!subscriberInfo.eventName ||
(subscriberInfo.eventName === eventName &&
subscriberInfo.sender.name === sender.name) ||
!subscriberInfo.sender
) {
process.stdout.write(`${subscriberInfo.subscriber.name}: `);
console.log(
`${subscriberInfo.subscriber.name}: ${eventName} event from ${
sender.name
} userData = ${JSON.stringify(userInfo)} `
);
result = subscriberInfo.handler(newEvent);
}
});
return result;
}postEvent ํจ์๋ ์ด๋ฏธ ์ถ๊ฐ์ํจ ์ด๋ฒคํธ์ ๋ํด์ ์คํ์์ผ์ฃผ๋ ๋ฉ์๋์ด๋ค. ํด๋น ๋ฉ์๋์์๋ eventName, sender ์ธ์คํด์ค, userInfo ๊ฐ์ฒด๋ฅผ ๋ฐ๋๋ค. ๊ทธ๋ฌ๋ฉด ๋ฐ์ ์ธ์๋ฅผ ํ ๋๋ก ์๋ก์ด Event๋ผ๋ ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ , ์ด์ ๋ํด ํธ๋ค๋ฌ ํจ์๊ฐ ํด๋ก์ ๋ก ์คํ๋ ์ ์๊ฒ๋ ํด์ฃผ์๋ค.
์ด ๋, ๋งจ ์ฒ์์ eventName์ด "" ๋น ๋ฌธ์์ด๋ก ๋์ด ์์ผ๋ฉด ๋ชจ๋ ์ด๋ฒคํธ๋ฅผ ์คํ์์ผ์ฃผ์ด์ผ ํ๊ณ , Publisher์ ์ด๋ฆ์ด undefined๋ผ๋ฉด ๋ชจ๋ ์ด๋ฒคํธ๋ฅผ ์คํ์์ผ ์ฃผ์ด์ผ ํ๋ค.
์ฒ์์๋ ์ด๋ฌํ ๋ฐฐ์ด์ ๋ง๋ค๊ธฐ ์ ์ ๋ค์ด์ค๋ ๊ฐ์ ๋ํด ๊ฒ์ฌ๋ฅผ ํ๊ณ , ๋น ๋ฌธ์์ด์ ๊ฒฝ์ฐ์ undefined๋ Publisher์ ์ด๋ฆ์ ๋ํด์ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๊ณ ์ํฉ์ ๋ง๊ฒ ๋ชจ๋ ์ด๋ฒคํธ๋ค์ ๋ง๋ค์ด๋ผ๊น ์๊ฐ์ ํ์๋๋ฐ, ์ด๋ ๊ฒ ๋
ผ๋ฆฌ์ฐ์ฐ์์ ๊ณ ์ฐจํจ์์ ์ด์ฉ์ ํตํด์ ์ด๋ฒคํธ๋ฅผ ์คํ์์ผ์ฃผ๋ ํธ์ด ๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ์ด ๋์ง ์์๋ ์๊ฐํ๋ค.
remove ํจ์
remove(subscriber) {
this.subscriberInfos = this.subscriberInfos.filter(
(subscriberInfo) => subscriberInfo.subscriber !== subscriber
);
}removeํจ์๋ filter ๊ณ ์ฐจํจ์๋ฅผ ์ด์ฉํด ๊ฐ๋จํ๊ฒ ๋น๊ตํ๋ฉด์ ๊ฑธ๋ฌ์ค ์ ์์๋ค
stringify ํจ์
์ฒ์์๋ stringify๊ฐ ์กฐ๊ฑด์ ์๋ ค์ฃผ๋ ํจ์๋ผ๊ธธ๋ ๋ฌด์จ ๋ง์ธ๊ฐ ํ๋ค. ์ด์ฐจํผ ์ด๋ฒคํธ๋ฅผ ์คํํ๋ ์กฐ๊ฑด์ ๊ฒฝ์ฐ๋ ์์์ ์ด๋ฒคํธ ์คํ ํจ์๋ฅผ ํตํด์ ๊ฑธ๋ฌ์ฃผ์๊ธฐ ๋๋ฌธ์ ์ด๋ค subscriber์ ์ด๋ค event๋ค์ด ๋ค์ด๊ฐ ์๋์ง๋ฅผ ์๋ ค์ฃผ๋ ํจ์์ ๋๋ผ๊ณ ํด์ํด์ฟ.
stringify() {
this.subscriberInfos.forEach((subscriberInfo) => {
console.log(
`${subscriberInfo.subscriber.name} : event name = "${subscriberInfo.eventName}", sender = ${subscriberInfo.sender.name}`
);
});
}addSubscriber , callEvent ํจ์
addSubscriber(subscriber, eventName, sender, handler) {
const subscribeData = {
subscriber: subscriber,
eventName: eventName,
sender: sender,
handler: handler,
};
if (
this.subscriberInfos.some((subInfo) =>
this.#checkValidity(subInfo, subscribeData)
)
) {
return;
}
this.subscriberInfos.push(subscribeData);
this.eventEmitter.on(eventName, (data) =>
this.postEvent(eventName, sender, data)
);
return;
}
callEvent(eventName, data) {
const result = this.eventEmitter.emit(eventName, data);
if (!result) return { data: null, completed: false };
return { data: result, completed: true };
}addSubscriber ํจ์๋ ์กฐ๊ธ ๋ ์ฌ์ฉ์ฑ์ด ์ข๋๋ก ๋ง๋ค์ด์ง ํจ์์ด๋ค. ๊ธฐ์กด add ํจ์์ ๋ฐ์ ํ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
๊ธฐ์กด์ add์ ๋ง์ง๋ง๊น์ง ๊ฑฐ์ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ์ง๋
์ง๋ง, ํฐ ์ฐจ์ด์ ์ eventEmmiter์ ์ ๋ฌด์ด๋ค. eventEmmiter๋ฅผ ์ฌ์ฉํ์ฌ ํด๋น ์ด๋ฒคํธ์ ๋ํด์ ์คํํ ์ ์๋ ํจ์๋ฅผ ๋ณด๋ค ๊ฐ๊ฒฐํ๊ฒ callEvent(์ด๋ฒคํธ ์ด๋ฆ, ๋ฐ์ดํฐ)์ ํํ๋ก ํธ์ถํ ์ ์๋๋ก ํด์ฃผ์ด ๋ค๋ฅธ ์ ๋ณด๋ค์ ๊ตณ์ด ๊ธฐ์ฌํ ๋ถํธํจ์ ๊ฐ์์์ผฐ๋ค.
AsyncPostEvent ํจ์
asyncPostEvent(eventName, sender, userInfo) {
return new Promise((resolve, reject) => {
const result = this.postEvent(eventName, sender, userInfo);
console.log(result);
if (!result) reject({ completed: false });
resolve(result);
});
}ํด๋น ํจ์๋ postEvent์ ๊ฐ์ ์ญํ ์ ํ์ง๋ง ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ๊ตฌํ๋ ํจ์์ด๋ค.
์ฐจ์ด์ ์ Promise๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ .then์ด๋ async/await ํค์๋๋ฅผ ํตํด ๋๊ธฐ ๋ฐฉ์์ผ๋ก ๋ฐ๊ฟ์ค ํ์ ์ฌ์ฉํด์ผ ํ๋ค.
delayPostEvent ํจ์
delayPostEvent(eventName, sender, userInfo, timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(this.postEvent(eventName, sender, userInfo));
}, timeout);
});
}
}delayPostEvent๋ ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ postEvent ํจ์ ๋ฒ์ ์ ์๊ฐ์ง์ฐ๊น์ง ์ถ๊ฐํ ๋ฒ์ ์ด๋ค. setTimeout์ ์ฌ์ฉํด ๋น๋๊ธฐ์ ์ผ๋ก ํจ์๋ฅผ ํธ์ถํ๊ณ , ํด๋นํ๋ ์๊ฐ์ด ์ง๋๋ฉด ๊ฐ์ resolve ์์ผ์ฃผ์๋ค.
Subscriber ํด๋์ค
class Subscriber {
constructor(name) {
this.name = name;
}
}
module.exports = { Subscriber };
ํ~ ๊ตณ์ด ์ธ์คํด์คํ์ํค์ง ๋ง๊ณ ๊ฐ์ฒด ํํ๋ก ๋ง๋ค์ด๋ ์ข์์๋ฏโฆ?
Publisher ํด๋์ค
const { EventManager, sharedInstance } = require("./eventManager");
class Publisher {
constructor(name) {
this.name = name;
this.eventManager = sharedInstance();
}
postEventPub(eventName, userInfo) {
return this.eventManager.postEvent(eventName, this, userInfo);
}
asyncPostEventPub(eventName, userInfo) {
return this.eventManager.asyncPostEvent(eventName, this, userInfo);
}
delayPostEventPub(eventName, userInfo) {
return this.eventManager.delayPostEvent(eventName, this, userInfo);
}
}
module.exports = { Publisher };
์ ๊ทธ๋๋ง ์๋ ํธ์ด๋ค. postEventํจ์๋ค์ ๋ํด์ ๋จผ์ Publisher์ ๋ฉ์๋๋ฅผ ํตํด ์คํํ๊ณ eventManager์ ์ง์ ํ์ฌ ํด๋นํ๋ ์ด๋ฒคํธ๋ฅผ ์คํํ๋ ๋ฐฉ์์ผ๋ก ์ค๊ณํ์๋ค.
ํ ์คํธ ์ฝ๋ ์์ฑ
jest.setTimeout(30000);
const { EventManager } = require("./eventManager");
const { Publisher } = require("./publisher");
const { Subscriber } = require("./subscriber");
const eventManager = new EventManager();
const subscriber0 = new Subscriber("testSub0");
const publisher0 = new Publisher("testPub0");
describe("ํ
์คํธ", () => {
test("eventManager ๋์ผ์ฑ ํ
์คํธ", () => {
const eventManagerA = new EventManager();
const eventManagerB = new EventManager();
expect(eventManagerA).toEqual(eventManagerB);
});
test("Subscriber ๋ฑ๋ก ํ
์คํธ", () => {
eventManager.add(subscriber0, "test", publisher0, (newEvent) => {
return "success";
});
expect(eventManager.subscriberInfos.length).toBe(1);
});
test("Subscriber ์ค๋ณต ๋ฑ๋ก ํ
์คํธ", () => {
eventManager.add(subscriber0, "test", publisher0, () => {
return "success";
});
expect(eventManager.subscriberInfos.length).toBe(1);
});
test("postEvent ๋์ ํ
์คํธ", () => {
expect(publisher0.postEventPub("test", { content: "content" })).toBe(
"success"
);
});
test("remove ๋์ ํ
์คํธ", () => {
eventManager.remove(subscriber0);
expect(eventManager.subscriberInfos.length).toBe(0);
});
test("EventEmmiter ํ
์คํธ", () => {
eventEmitter.emit("addSub", "testSub0", "event", "testPub0", () => {
console.log("test");
});
});
test("addSubscriber ํ
์คํธ", () => {
eventManager.addSubscriber(subscriber0, "emmiterTest", publisher0, () => {
console.log("์ด๋ฒคํธ ๋ฑ๋ก ํ ์คํ");
});
expect(eventManager.callEvent("emmiterTest", { data: "wwww" }).data).toBe(
true
);
});
test("asyncPostEvent ํ
์คํธ", async () => {
eventManager.add(subscriber0, "asyncPostEvent test", publisher0, () => {
return "asyncPostEvent Test";
});
const result = await eventManager.asyncPostEvent(
"asyncPostEvent test",
publisher0,
{
data: "async test",
}
);
expect(result).toBe("asyncPostEvent Test");
});
test("delayPostEvent ํ
์คํธ", async () => {
eventManager.add(subscriber0, "delayPostEvent test", publisher0, () => {
return "delayPostEvent Test";
});
const result = await eventManager.delayPostEvent(
"delayPostEvent test",
publisher0,
{
data: "delay test",
},
3000
);
console.log(result);
expect(result).toBe("delayPostEvent Test");
});
test("๋น๋๊ธฐ ๋์ ์๋๋ฆฌ์ค ํ
์คํธ1", () => {
const subscriberA = new Subscriber("subscriberA");
const albumModel = new Publisher("albumModel");
eventManager.addSubscriber(
subscriberA,
"ModelDataChanged",
albumModel,
(obj) => {
return obj.eventName;
}
);
const result = eventManager.callEvent("ModelDataChanged", {
data: "ModelDataChanged Event",
});
expect(result.data).toBe(true);
});
const testSub = new Subscriber("testSub");
test("๋น๋๊ธฐ ๋์ ์๋๋ฆฌ์ค ํ
์คํธ2(๋๊ธฐ)", () => {
console.log("222");
const albumTableView = new Publisher("albumTableView");
eventManager.add(testSub, "syncEvent", albumTableView, (obj) => {
return obj.eventName;
});
expect(
eventManager.postEvent("syncEvent", albumTableView, {
user: "user",
})
).toBe("syncEvent");
});
const albumTableViewController = new Publisher("albumTableViewController");
test("๋น๋๊ธฐ ๋์ ์๋๋ฆฌ์ค ํ
์คํธ 3(๋น๋๊ธฐ)", async () => {
eventManager.add(testSub, "asyncEvent", albumTableViewController, (obj) => {
return obj.eventName;
});
expect(
await eventManager.asyncPostEvent(
"asyncEvent",
albumTableViewController,
{
user: "user",
}
)
).toBe("asyncEvent");
});
test("๋น๋๊ธฐ ๋์ ์๋๋ฆฌ์ค ํ
์คํธ 4(์ง์ฐ ๋น๋๊ธฐ)", async () => {
console.log("444");
const dummySub = new Subscriber("dummySub");
const dummyPub = new Publisher("dummyPub");
eventManager.add(dummySub, "delayPostEvent", dummyPub, (obj) => {
return obj.eventName;
});
const result = await eventManager.delayPostEvent(
"delayPostEvent",
dummyPub,
{
data: "delayPostEvent",
},
10000
);
expect(result).toBe("delayPostEvent");
});
});
ํ๊ธฐ
์ญ์ ๋๊ตฐ๊ฐ์ ํจ๊ป ํ๋ค๋๊ฑด ์ด๋ ต์ง๋ง ๋ง์ ํจ๊ป ๋ง๋ ๊ฒฐ๊ณผ๋ฌผ์ ๋ณด๋ ๋๋ฌด ๋ฟ๋ฏํ๋ค. ์ญ์ 1+1=2์ด๋ฏ์ด ๋๋๊ฐ ๋ชจ์ด๋ฉด ๋ ๋นจ๋ฆฌ, ์ข์ ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋๋๋ณด๋ค ์ด๋ฉด์ ๋์์ธํจํด์ ๋ํด์ ๋ง๋ง ๋ค์์ง, ์ง์ ๊ณต๋ถํ๊ณ ์จ๋ณผ ์ค์ ๋ชฐ๋๋๋ฐ ๋ด๊ฐ ๋ฒ์จ ์ด๋ฐ๊ฑธ ๊ณต๋ถํ ๋๋ผ๋..