Какие паттерны проектирования реализует EventEmitter в Node.js?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерны проектирования в EventEmitter
EventEmitter в Node.js - это не просто механизм обработки событий, это реализация нескольких классических паттернов проектирования, которые вместе создают мощную архитектурную абстракцию.
1. Observer Pattern (Паттерн Наблюдатель)
Это основной паттерн, лежащий в основе EventEmitter:
const { EventEmitter } = require("events");
// Изменение в одном объекте автоматически уведомляет всех наблюдателей
class UserService extends EventEmitter {
constructor() {
super();
this.users = [];
}
addUser(user) {
this.users.push(user);
// Уведомляем все заинтересованные части (observer)
this.emit("userAdded", user);
}
deleteUser(id) {
const user = this.users.find(u => u.id === id);
if (user) {
this.users = this.users.filter(u => u.id !== id);
this.emit("userDeleted", user);
}
}
}
const userService = new UserService();
// Разные части приложения слушают события
// Это наблюдатели (observers)
userService.on("userAdded", (user) => {
console.log(`[Logger] User added: ${user.name}`);
});
userService.on("userAdded", (user) => {
console.log(`[Analytics] Track user signup: ${user.id}`);
});
userService.on("userDeleted", (user) => {
console.log(`[Audit] User deleted: ${user.id}`);
});
// Добавление пользователя триггерит все наблюдатели
userService.addUser({ id: 1, name: "John" });
Преимущества:
- Слабая связь - UserService не знает о логгере или аналитике
- Масштабируемость - легко добавить новых наблюдателей
- Разделение ответственности - каждый слушатель отвечает за одно
2. Pub/Sub Pattern (Издатель-Подписчик)
Это расширение Observer, где издатели и подписчики полностью развязаны:
const { EventEmitter } = require("events");
// Центральный почтовый ящик
class EventBus extends EventEmitter {}
const eventBus = new EventBus();
// Издатели (Publishers)
class OrderService {
completeOrder(orderId) {
console.log(`Order ${orderId} completed`);
// Издатель не знает, кто его слушает
eventBus.emit("order:completed", { orderId, timestamp: Date.now() });
}
}
// Подписчики (Subscribers)
class EmailService {
constructor() {
// Подписывается на события
eventBus.on("order:completed", (order) => {
console.log(`[Email] Sending confirmation to customer for order ${order.orderId}`);
});
}
}
class InventoryService {
constructor() {
eventBus.on("order:completed", (order) => {
console.log(`[Inventory] Updating stock for order ${order.orderId}`);
});
}
}
class ReportingService {
constructor() {
eventBus.on("order:completed", (order) => {
console.log(`[Reporting] Recording sale for analytics`);
});
}
}
const orderService = new OrderService();
new EmailService();
new InventoryService();
new ReportingService();
orderService.completeOrder(123);
// Выведет все три подписчика, хотя OrderService их не знает
Это полная Pub/Sub система: издатель не знает подписчиков, подписчики не знают друг друга.
3. Command Pattern
Использование событий как команд для выполнения действий:
const { EventEmitter } = require("events");
class CommandBus extends EventEmitter {
execute(command, handler) {
this.on(command, handler);
}
dispatch(command, data) {
this.emit(command, data);
}
}
const commandBus = new CommandBus();
// Регистрируем команды
commandBus.execute("CreateUser", (userData) => {
console.log(`Creating user: ${userData.name}`);
// Логика создания
});
commandBus.execute("SendEmail", (emailData) => {
console.log(`Sending email to ${emailData.to}`);
});
commandBus.execute("LogEvent", (event) => {
console.log(`[LOG] ${event.message}`);
});
// Отправка команд
commandBus.dispatch("CreateUser", { name: "Alice" });
commandBus.dispatch("SendEmail", { to: "alice@example.com" });
commandBus.dispatch("LogEvent", { message: "User created" });
4. Mediator Pattern (Посредник)
EventEmitter используется как посредник между компонентами:
const { EventEmitter } = require("events");
class ChatRoom extends EventEmitter {
constructor(name) {
super();
this.name = name;
this.users = new Map();
}
addUser(user) {
this.users.set(user.id, user);
this.emit("user:joined", user);
}
sendMessage(fromUserId, message) {
this.emit("message:sent", {
from: this.users.get(fromUserId),
text: message,
room: this.name
});
}
}
class User {
constructor(id, name, chatRoom) {
this.id = id;
this.name = name;
this.chatRoom = chatRoom;
// Пользователь не знает о других пользователях напрямую
// Всё общение идёт через посредника (ChatRoom)
chatRoom.on("user:joined", (user) => {
if (user.id !== this.id) {
console.log(`${this.name} sees: ${user.name} joined`);
}
});
chatRoom.on("message:sent", (msg) => {
console.log(`${this.name} received: ${msg.from.name}: ${msg.text}`);
});
}
}
const room = new ChatRoom("General");
const alice = new User(1, "Alice", room);
const bob = new User(2, "Bob", room);
room.addUser(alice);
room.addUser(bob);
room.sendMessage(1, "Hi Bob!");
Посредник (ChatRoom) управляет взаимодействием между пользователями.
5. Repository Pattern с События
Использование событий для отслеживания изменений в данных:
const { EventEmitter } = require("events");
class Repository extends EventEmitter {
constructor(name) {
super();
this.name = name;
this.data = new Map();
}
create(id, item) {
this.data.set(id, item);
this.emit("created", { id, item });
return item;
}
update(id, updates) {
const item = this.data.get(id);
if (item) {
const updated = { ...item, ...updates };
this.data.set(id, updated);
this.emit("updated", { id, from: item, to: updated });
return updated;
}
}
delete(id) {
const item = this.data.get(id);
if (item) {
this.data.delete(id);
this.emit("deleted", { id, item });
return true;
}
return false;
}
}
const userRepo = new Repository("users");
// Event sourcing: отслеживание всех изменений
const changelog = [];
userRepo.on("created", (event) => {
changelog.push({ type: "CREATE", ...event, timestamp: Date.now() });
console.log(`[Audit] Created: ${event.id}`);
});
userRepo.on("updated", (event) => {
changelog.push({ type: "UPDATE", ...event, timestamp: Date.now() });
console.log(`[Audit] Updated: ${event.id}`);
});
userRepo.on("deleted", (event) => {
changelog.push({ type: "DELETE", ...event, timestamp: Date.now() });
console.log(`[Audit] Deleted: ${event.id}`);
});
userRepo.create(1, { name: "Alice" });
userRepo.update(1, { email: "alice@example.com" });
userRepo.delete(1);
console.log("Changelog:", changelog);
6. State Machine Pattern
Использование событий для управления состояниями:
const { EventEmitter } = require("events");
class OrderState extends EventEmitter {
constructor() {
super();
this.state = "pending";
}
place() {
if (this.state === "pending") {
this.state = "confirmed";
this.emit("placed");
}
}
ship() {
if (this.state === "confirmed") {
this.state = "shipped";
this.emit("shipped");
}
}
deliver() {
if (this.state === "shipped") {
this.state = "delivered";
this.emit("delivered");
}
}
cancel() {
if (["pending", "confirmed"].includes(this.state)) {
this.state = "cancelled";
this.emit("cancelled");
}
}
}
const order = new OrderState();
order.on("placed", () => console.log("Order placed!"));
order.on("shipped", () => console.log("Package shipped!"));
order.on("delivered", () => console.log("Delivered!"));
order.on("cancelled", () => console.log("Order cancelled"));
order.place(); // Событие: placed
order.ship(); // Событие: shipped
order.deliver(); // Событие: delivered
7. Chain of Responsibility
Передача события по цепочке обработчиков:
const { EventEmitter } = require("events");
class RequestLogger extends EventEmitter {
constructor(next) {
super();
this.next = next;
}
handle(request) {
console.log(`[Logger] Request: ${request.method} ${request.path}`);
this.emit("handled", request);
if (this.next) this.next.handle(request);
}
}
class AuthValidator extends EventEmitter {
constructor(next) {
super();
this.next = next;
}
handle(request) {
if (request.token) {
console.log(`[Auth] Token valid`);
this.emit("handled", request);
if (this.next) this.next.handle(request);
} else {
console.log(`[Auth] No token`);
this.emit("error", "Unauthorized");
}
}
}
class BusinessLogic extends EventEmitter {
handle(request) {
console.log(`[Logic] Processing request`);
this.emit("success", { status: 200, data: "Success" });
}
}
const logic = new BusinessLogic();
const auth = new AuthValidator(logic);
const logger = new RequestLogger(auth);
logger.handle({ method: "GET", path: "/users", token: "abc123" });
Сравнение паттернов
| Паттерн | Использование | Преимущество |
|---|---|---|
| Observer | Один объект уведомляет многих | Слабая связь |
| Pub/Sub | Полная развязка издателя и подписчика | Максимальная модульность |
| Command | Инкапсуляция действий | Отмена, повтор операций |
| Mediator | Координация компонентов | Упрощает взаимодействие |
| State Machine | Управление состояниями | Гарантирует корректные переходы |
| Chain of Responsibility | Цепочка обработчиков | Гибкость в обработке |
Best Practices
- Используй семантичные имена событий:
user:created, неevent1 - Избегай глобальных EventEmitter - это усложняет отладку
- Типизируй события в TypeScript для безопасности
- Ограничивай слушателей - используй
once()если событие один раз - Обрабатывай ошибки - добавляй
on("error")обработчик
EventEmitter - это не просто механизм callback, это фундамент асинхронной архитектуры Node.js, реализующий мощные паттерны для построения масштабируемых приложений.