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

Как можно использовать инкапсуляцию?

1.7 Middle🔥 181 комментариев
#Архитектура и паттерны#ООП

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

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

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

Инкапсуляция в JavaScript и Node.js

Инкапсуляция — это один из основных принципов объектно-ориентированного программирования, который скрывает внутренние детали реализации и предоставляет контролируемый доступ к данным через публичный интерфейс. В JavaScript это можно реализовать несколькими способами.

1. Приватные поля в классах (ES2022)

Современный и рекомендуемый подход с использованием префикса #:

class BankAccount {
  #balance = 0;  // приватное поле
  #pin = null;   // приватное поле
  
  constructor(initialBalance, pin) {
    this.#balance = initialBalance;
    this.#pin = pin;
  }
  
  // Публичный метод
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
      this.#logTransaction('deposit', amount);
      return true;
    }
    return false;
  }
  
  // Публичный метод
  withdraw(amount, pin) {
    if (this.#verifyPin(pin) && amount <= this.#balance) {
      this.#balance -= amount;
      this.#logTransaction('withdraw', amount);
      return true;
    }
    return false;
  }
  
  // Публичный метод для чтения баланса
  getBalance(pin) {
    if (this.#verifyPin(pin)) {
      return this.#balance;
    }
    throw new Error('Invalid PIN');
  }
  
  // Приватный метод
  #verifyPin(pin) {
    return this.#pin === pin;
  }
  
  // Приватный метод
  #logTransaction(type, amount) {
    console.log(`[${new Date().toISOString()}] ${type}: ${amount}`);
  }
}

// Использование
const account = new BankAccount(1000, '1234');
console.log(account.deposit(500));           // true
console.log(account.withdraw(200, '1234'));  // true
console.log(account.getBalance('1234'));      // 1300

// Ошибка: доступ к приватному полю невозможен
console.log(account.#balance);  // SyntaxError

2. Замыкания (Closures)

Классический подход, работает во всех версиях JavaScript:

function createCounter() {
  let count = 0;  // приватная переменная в замыкании
  
  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.getCount());   // 2

// Нельзя получить count напрямую
console.log(counter.count);  // undefined

Этот паттерн часто используется в Node.js модулях:

// db.js
const connection = require('some-db');
const dbInstance = null;  // приватная переменная

module.exports = {
  connect(config) {
    dbInstance = connection.create(config);
    return dbInstance;
  },
  
  query(sql) {
    if (!dbInstance) {
      throw new Error('Database not connected');
    }
    return dbInstance.query(sql);
  },
  
  disconnect() {
    if (dbInstance) {
      dbInstance.close();
      dbInstance = null;
    }
  }
};

3. WeakMap для приватных данных

Элегантный способ скрыть данные для экземпляров класса:

const privateData = new WeakMap();

class User {
  constructor(username, password) {
    this.username = username;  // публичное
    
    // Приватные данные хранятся в WeakMap
    privateData.set(this, {
      password: password,
      tokens: [],
      lastLogin: null
    });
  }
  
  authenticate(password) {
    const data = privateData.get(this);
    return data.password === password;
  }
  
  addToken(token) {
    const data = privateData.get(this);
    data.tokens.push(token);
  }
  
  getTokens() {
    const data = privateData.get(this);
    return [...data.tokens];  // возвращаем копию
  }
}

const user = new User('john', 'secret123');
console.log(user.authenticate('secret123'));  // true
console.log(user.username);  // john
console.log(privateData.get(user).password);  // secret123

4. Getters и Setters для контроля доступа

Дают возможность добавить валидацию при доступе к свойствам:

class Temperature {
  #celsius = 0;
  
  // Getter
  get celsius() {
    return this.#celsius;
  }
  
  // Setter с валидацией
  set celsius(value) {
    if (typeof value !== 'number') {
      throw new TypeError('Temperature must be a number');
    }
    if (value < -273.15) {
      throw new RangeError('Temperature cannot be below absolute zero');
    }
    this.#celsius = value;
  }
  
  get fahrenheit() {
    return (this.#celsius * 9/5) + 32;
  }
  
  set fahrenheit(value) {
    this.celsius = (value - 32) * 5/9;
  }
}

const temp = new Temperature();
temp.celsius = 25;  // использует setter
console.log(temp.celsius);     // 25 (getter)
console.log(temp.fahrenheit);  // 77

// Валидация работает
temp.celsius = -300;  // RangeError: Temperature cannot be below absolute zero

5. Абстрактные классы с приватными методами

Для создания правил наследования:

class Database {
  #connection = null;
  
  // Публичный метод
  async connect(config) {
    await this.#validateConfig(config);
    this.#connection = await this.#createConnection(config);
  }
  
  // Публичный метод
  async query(sql, params = []) {
    if (!this.#connection) {
      throw new Error('Database not connected');
    }
    return this.#executeQuery(sql, params);
  }
  
  // Приватные методы
  async #validateConfig(config) {
    if (!config.host || !config.port) {
      throw new Error('Invalid config');
    }
  }
  
  async #createConnection(config) {
    // имплементация
  }
  
  async #executeQuery(sql, params) {
    // имплементация
  }
}

class PostgreSQL extends Database {
  // может наследовать public методы, не видит приватные
}

6. Интерфейсы в TypeScript

Для строгой инкапсуляции с типизацией:

interface IUser {
  getEmail(): string;
  authenticate(password: string): boolean;
}

class User implements IUser {
  private email: string;
  private passwordHash: string;
  private tokens: string[] = [];
  
  constructor(email: string, passwordHash: string) {
    this.email = email;
    this.passwordHash = passwordHash;
  }
  
  public getEmail(): string {
    return this.email;
  }
  
  public authenticate(password: string): boolean {
    return this.validatePassword(password);
  }
  
  private validatePassword(password: string): boolean {
    // валидация с хешированием
    return true;
  }
}

const user: IUser = new User('john@example.com', 'hash123');
console.log(user.getEmail());  // john@example.com
// user.email не доступен (TypeScript ошибка)

7. Принцип инкапсуляции в архитектуре

На уровне модулей и сервисов:

// userService.js - публичный интерфейс
const userRepository = require('./userRepository');
const emailService = require('./emailService');
const passwordUtils = require('./passwordUtils');  // приватные утилиты

module.exports = {
  async register(email, password) {
    // публичный метод
    const hashedPassword = await passwordUtils.hash(password);
    return userRepository.create(email, hashedPassword);
  },
  
  async authenticate(email, password) {
    const user = await userRepository.findByEmail(email);
    if (!user) return null;
    
    const isValid = await passwordUtils.compare(password, user.passwordHash);
    if (isValid) {
      return { id: user.id, email: user.email };
    }
    return null;
  }
  
  // приватная функция (не экспортируется)
};

// app.js - использует только публичный интерфейс
const userService = require('./userService');
await userService.register('user@example.com', 'password123');

Сравнение подходов

ПодходПоддержкаПроизводительностьИспользование
# приватные поляES2022+БыстроРекомендуется
ЗамыканияВсе версииХорошоМодули, IIFE
WeakMapES6+НормальноСпецифичные случаи
Getters/SettersES5+ХорошоВалидация доступа
TypeScript privateTypeScriptЗависитСтрогая типизация

Лучшие практики инкапсуляции

  1. Минимизируйте публичный интерфейс — экспортируйте только необходимое
  2. Используйте приватные поля — для новых проектов с ES2022+
  3. Добавляйте валидацию — через getters и setters
  4. Документируйте API — что публичное, что приватное
  5. Избегайте нарушения инкапсуляции — не обращайтесь к внутренним деталям
// Хороший пример
class API {
  #apiKey = null;
  
  constructor(apiKey) {
    this.#apiKey = apiKey;
  }
  
  async request(endpoint, method = 'GET') {
    return this.#makeRequest(endpoint, method);
  }
  
  #makeRequest(endpoint, method) {
    // имплементация с использованием приватного apiKey
  }
}

Заключение

Инкапсуляция в JavaScript реализуется разными способами в зависимости от версии языка и требований проекта. Современный подход использует приватные поля с #, старые проекты полагаются на замыкания. Правильная инкапсуляция делает код безопаснее, поддерживаемее и предсказуемее.