Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Объектно-ориентированное программирование (ООП) в Node.js
Да, ООП — один из фундаментальных парадигм программирования, который я активно использую в Node.js backend разработке. Расскажу о четырёх столпах ООП и их практическом применении.
Четыре столпа ООП
1. Инкапсуляция (Encapsulation)
Скрытие внутренних деталей реализации и предоставление контролируемого интерфейса:
class PaymentProcessor {
#apiKey; // Приватное поле (скрыто)
#baseUrl; // Приватное поле
#retryCount = 3; // Приватное с дефолтом
constructor(apiKey, baseUrl) {
this.#apiKey = apiKey; // НИКОГДА не выставляем API key наружу
this.#baseUrl = baseUrl;
}
// Публичный метод — контролируемый доступ
async processPayment(amount, cardToken) {
if (amount <= 0) {
throw new Error('Amount must be positive');
}
return this.#sendRequest({
amount,
card: cardToken // Не передаём сырые данные
});
}
// Приватный метод — внутренняя реализация
async #sendRequest(payload) {
for (let i = 0; i < this.#retryCount; i++) {
try {
return await fetch(`${this.#baseUrl}/process`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.#apiKey}`
},
body: JSON.stringify(payload)
});
} catch (error) {
if (i === this.#retryCount - 1) throw error;
await this.#sleep(Math.pow(2, i) * 1000); // exponential backoff
}
}
}
#sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Использование
const processor = new PaymentProcessor(process.env.PAYMENT_API_KEY, 'https://api.payment.com');
await processor.processPayment(100, tokenFromCard);
// processor.#apiKey = 'hacked'; // ❌ Ошибка: не можем изменить приватное поле
2. Наследование (Inheritance)
Реиспользование кода через иерархию классов:
// Базовый класс (родитель)
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
speak() {
return `${this.name} makes a sound`;
}
sleep() {
console.log(`${this.name} is sleeping`);
}
}
// Наследуемый класс (дочерний)
class Dog extends Animal {
constructor(name, breed) {
super(name, 'dog'); // Вызываем конструктор родителя
this.breed = breed;
}
speak() {
// Переопределяем метод родителя
return `${this.name} barks: Woof!`;
}
fetch(object) {
return `${this.name} fetches the ${object}`;
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
console.log(dog.speak()); // "Buddy barks: Woof!"
dog.sleep(); // "Buddy is sleeping"
console.log(dog.fetch('ball')); // "Buddy fetches the ball"
// Практический пример: иерархия сервисов
class BaseService {
constructor(database) {
this.db = database;
}
async getById(id) {
return await this.db.query(`SELECT * FROM ${this.tableName} WHERE id = $1`, [id]);
}
async create(data) {
return await this.db.query(
`INSERT INTO ${this.tableName} (${Object.keys(data).join(',')}) VALUES (...)`,
Object.values(data)
);
}
get tableName() {
throw new Error('tableName must be defined in child class');
}
}
class UserService extends BaseService {
get tableName() {
return 'users';
}
async findByEmail(email) {
return await this.db.query('SELECT * FROM users WHERE email = $1', [email]);
}
}
class ProductService extends BaseService {
get tableName() {
return 'products';
}
async findByCategory(category) {
return await this.db.query('SELECT * FROM products WHERE category = $1', [category]);
}
}
3. Полиморфизм (Polymorphism)
Один интерфейс, разные реализации:
class NotificationService {
// Базовый класс
async send(recipient, message) {
throw new Error('send() must be implemented');
}
}
class EmailNotification extends NotificationService {
async send(recipient, message) {
// Отправляем email
console.log(`Email to ${recipient}: ${message}`);
await emailClient.send(recipient, message);
}
}
class SMSNotification extends NotificationService {
async send(recipient, message) {
// Отправляем SMS
console.log(`SMS to ${recipient}: ${message}`);
await smsClient.send(recipient, message);
}
}
class SlackNotification extends NotificationService {
async send(recipient, message) {
// Отправляем в Slack
console.log(`Slack to ${recipient}: ${message}`);
await slackClient.send(recipient, message);
}
}
// Полиморфизм в действии
const notifyUser = async (type, recipient, message) => {
let notifier;
switch (type) {
case 'email':
notifier = new EmailNotification();
break;
case 'sms':
notifier = new SMSNotification();
break;
case 'slack':
notifier = new SlackNotification();
break;
default:
throw new Error('Unknown notification type');
}
// Один метод, разные реализации
await notifier.send(recipient, message);
};
// Или используя Strategy паттерн (ещё лучше)
class Notification {
constructor(strategy) {
this.strategy = strategy;
}
async send(recipient, message) {
return this.strategy.send(recipient, message);
}
}
const emailNotif = new Notification(new EmailNotification());
await emailNotif.send('user@example.com', 'Hello!');
4. Абстракция (Abstraction)
Скрытие сложности, предоставление упрощённого интерфейса:
// Сложная реализация скрыта
class Database {
async query(sql, params) {
// Здесь могут быть миграции, логирование, кеширование, и т.д.
const pool = await this.getConnectionPool();
const connection = await pool.acquire();
try {
const result = await connection.execute(sql, params);
return result;
} finally {
await pool.release(connection);
}
}
async transaction(callback) {
const connection = await this.getConnection();
await connection.query('BEGIN');
try {
const result = await callback(connection);
await connection.query('COMMIT');
return result;
} catch (error) {
await connection.query('ROLLBACK');
throw error;
}
}
}
// Пользователь видит только простой интерфейс
const db = new Database();
const users = await db.query('SELECT * FROM users');
await db.transaction(async (conn) => {
// Делаем несколько операций атомарно
});
SOLID принципы в ООП
S — Single Responsibility: Один класс — одна ответственность
// ❌ Плохо: слишком много ответственности
class UserManager {
async createUser(email, password) { /* ... */ }
async sendEmail(to, subject) { /* ... */ }
async logActivity(userId, action) { /* ... */ }
}
// ✅ Хорошо: разделённая ответственность
class UserService {
async createUser(email, password) { /* ... */ }
}
class EmailService {
async send(to, subject) { /* ... */ }
}
class ActivityLogger {
async log(userId, action) { /* ... */ }
}
O — Open/Closed: Открыт для расширения, закрыт для модификации
// ❌ Плохо: нужно менять класс для каждого нового типа
class PaymentProcessor {
process(type, amount) {
if (type === 'card') {
// процесс для карты
} else if (type === 'paypal') {
// процесс для PayPal
}
}
}
// ✅ Хорошо: расширяем без изменения базового класса
class PaymentGateway {
constructor(adapter) {
this.adapter = adapter;
}
async process(amount) {
return this.adapter.process(amount);
}
}
const cardProcessor = new PaymentGateway(new CardAdapter());
const paypalProcessor = new PaymentGateway(new PayPalAdapter());
D — Dependency Inversion: Зависим от абстракций, не конкретных реализаций
// ❌ Плохо: зависит от конкретного класса
class UserController {
constructor() {
this.userService = new UserService(); // Жёсткая зависимость
}
}
// ✅ Хорошо: инъекция зависимостей
class UserController {
constructor(userService) {
this.userService = userService; // Зависим от интерфейса, не реализации
}
}
const controller = new UserController(new UserService());
Когда ООП полезно в Node.js
- Сложная бизнес-логика — классы структурируют код
- Множество сущностей — модели, сервисы, контроллеры
- Долгоживущие проекты — архитектура важнее
- Крупные команды — общие стандарты
Когда функциональное программирование лучше
- Простые функции трансформации — чистые функции
- Параллельные вычисления — immutable data
- Быстрое прототипирование — меньше overhead
Мой подход: ООП для архитектуры приложения (services, models, controllers) + функциональное программирование для обработки данных (map, filter, reduce). Это лучший из двух миров.