← Назад к вопросам

Слышал ли про ООП

1.0 Junior🔥 111 комментариев
#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Объектно-ориентированное программирование (ООП) в 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). Это лучший из двух миров.

Слышал ли про ООП | PrepBro