Ответ
В JavaScript, особенно в контексте Node.js, вместо классического наследования классов часто используют композицию. Это означает сборку объектов из более мелких, независимых частей, что делает код более гибким и модульным.
Пример композиции в Node.js-проекте:
// logger.js - независимый модуль
const createLogger = (prefix) => ({
log: (message) => console.log(`[${prefix}] ${message}`)
});
// userService.js - сервис, использующий композицию
const createUserService = (logger) => ({
createUser: async (name) => {
logger.log(`Создание пользователя: ${name}`);
// Логика создания, например, запрос к БД
const newUser = await db.User.create({ name });
logger.log(`Пользователь ${newUser.id} создан`);
return newUser;
}
});
// index.js - сборка приложения
const logger = createLogger('UserService');
const userService = createUserService(logger);
// Использование
await userService.createUser('Alice');
Другие популярные альтернативы в экосистеме Node.js:
- Фабричные функции: Как в примере выше, для создания объектов с инкапсулированным состоянием.
- Миксины через
Object.assign()или spread-оператор: Для добавления функциональности в объект. - Функциональное программирование: Композиция чистых функций (например, с помощью библиотек типа
lodash/fpилиramda). - Модульная система CommonJS/ES Modules: Разделение логики на небольшие, слабосвязанные файлы.
Почему композиция часто предпочтительнее в Node.js?
- Гибкость: Легко заменить или модифицировать часть функциональности без переписывания иерархии классов.
- Тестируемость: Независимые модули легко тестировать изолированно (мокать или стабить).
- Избегание хрупких базовых классов: Изменение родительского класса может сломать множество наследников.
- Лучшее соответствие принципам SOLID, особенно принципу единственной ответственности (SRP) и разделению интерфейсов (ISP).