Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Инкапсуляция в программировании
Инкапсуляция — это один из основных принципов объектно-ориентированного программирования, который заключается в объединении данных и методов, работающих с этими данными, внутри одного объекта, и в скрытии внутренней реализации от внешнего кода. Это создаёт чёткие границы между внутренним состоянием объекта и его публичным интерфейсом.
Основная идея
Инкапсуляция работает по принципу "чёрного ящика":
- Внешний код не должен знать, как работает объект изнутри
- Объект предоставляет контролируемый интерфейс для взаимодействия
- Внутреннее состояние защищено от неправильного использования
// ❌ Без инкапсуляции
const user = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
};
// Кто угодно может изменить данные неправильно
user.age = -50; // Отрицательный возраст?
user.email = 'not-an-email'; // Некорректный email?
// ✅ С инкапсуляцией
class User {
#age; // приватное свойство
#email; // приватное свойство
constructor(name, age, email) {
this.name = name;
this.setAge(age); // валидация через метод
this.setEmail(email); // валидация через метод
}
setAge(age) {
if (age < 0 || age > 150) {
throw new Error('Некорректный возраст');
}
this.#age = age;
}
getAge() {
return this.#age;
}
setEmail(email) {
if (!email.includes('@')) {
throw new Error('Некорректный email');
}
this.#email = email;
}
getEmail() {
return this.#email;
}
}
const user = new User('Alice', 25, 'alice@example.com');
user.getAge(); // 25
user.setAge(-50); // Ошибка: Некорректный возраст
Три уровня доступа
1. Public (публичные свойства и методы)
Доступны всем, могут быть использованы и изменены извне.
class Person {
// Public свойство
name = 'John';
// Public метод
introduce() {
return `Меня зовут ${this.name}`;
}
}
const person = new Person();
person.name = 'Alice'; // Можно менять
person.introduce(); // Можно вызывать
2. Private (приватные свойства и методы)
Доступны только внутри самого класса. Обозначаются с помощью # (ES2022).
class BankAccount {
#balance = 0; // Приватное свойство
#validateAmount(amount) { // Приватный метод
if (amount <= 0) {
throw new Error('Сумма должна быть положительной');
}
}
deposit(amount) {
this.#validateAmount(amount); // Используется внутри класса
this.#balance += amount;
}
getBalance() {
return this.#balance; // Публичный доступ к приватным данным
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
console.log(account.#balance); // Ошибка: приватное свойство
3. Protected (защищённые свойства и методы)
В JavaScript нет встроенного ключевого слова protected, но часто используют соглашение с _ префиксом.
class Animal {
_name = ''; // "защищённое" свойство (соглашение)
_makeSound() { // "защищённый" метод
return 'Какой-то звук';
}
speak() {
return this._makeSound();
}
}
class Dog extends Animal {
speak() {
return 'Гав! ' + this._makeSound();
}
}
Примеры инкапсуляции
Пример 1: Шкаф с замком
class Safe {
#contents = [];
#isLocked = true;
#password = 'secret123';
unlock(password) {
if (password === this.#password) {
this.#isLocked = false;
return 'Сейф открыт';
}
return 'Неверный пароль';
}
lock() {
this.#isLocked = true;
return 'Сейф закрыт';
}
addItem(item) {
if (this.#isLocked) {
throw new Error('Сейф заперт');
}
this.#contents.push(item);
}
getContents() {
if (this.#isLocked) {
throw new Error('Сейф заперт');
}
return [...this.#contents]; // возвращаем копию, не оригинал
}
}
const safe = new Safe();
safe.unlock('secret123');
safe.addItem('документы');
const items = safe.getContents();
safe.lock();
Пример 2: Класс для работы с датами
class Date {
#day;
#month;
#year;
constructor(day, month, year) {
this.setDate(day, month, year);
}
setDate(day, month, year) {
if (month < 1 || month > 12) {
throw new Error('Месяц должен быть от 1 до 12');
}
if (day < 1 || day > 31) {
throw new Error('День должен быть от 1 до 31');
}
this.#day = day;
this.#month = month;
this.#year = year;
}
getDay() { return this.#day; }
getMonth() { return this.#month; }
getYear() { return this.#year; }
toString() {
return `${this.#day}.${this.#month}.${this.#year}`;
}
}
const date = new Date(22, 3, 2025);
console.log(date.toString()); // 22.3.2025
Пример 3: React компонент с приватным состоянием
class Counter extends React.Component {
#internalValue = 0; // приватное поле класса
constructor(props) {
super(props);
this.state = {
count: 0,
history: [] // приватное состояние (не выставляется напрямую)
};
}
#recordHistory() { // приватный метод
this.setState(prev => ({
history: [...prev.history, prev.count]
}));
}
increment = () => {
this.setState(prev => ({
count: prev.count + 1
}));
this.#recordHistory(); // используется только внутри
}
getHistory() { // публичный метод для доступа к приватным данным
return this.state.history;
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
Инкапсуляция в функциональных компонентах
// Используем замыкание для инкапсуляции
function createCounter() {
let count = 0; // приватная переменная
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2
counter.count = 100; // это не повлияет на приватную переменную
counter.getCount(); // всё ещё 2
Getters и Setters
class Temperature {
#celsius = 0;
// Setter
set celsius(value) {
if (value < -273) {
throw new Error('Температура не может быть ниже абсолютного нуля');
}
this.#celsius = value;
}
// Getter
get celsius() {
return this.#celsius;
}
get fahrenheit() {
return (this.#celsius * 9/5) + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) * 5/9;
}
}
const temp = new Temperature();
temp.celsius = 25;
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 32;
console.log(temp.celsius); // 0
Преимущества инкапсуляции
- Защита данных: внутреннее состояние защищено от неправильного использования
- Валидация: все изменения проходят через контролируемые методы
- Гибкость: можно изменить внутреннюю реализацию без влияния на внешний код
- Безопасность: уменьшается риск случайного повреждения данных
- Понятнее: ясный публичный интерфейс, скрытые сложности
Плохие практики (антипаттерны)
// ❌ Плохо: прямой доступ к данным
class BadUser {
age = 0; // публичное, может быть установлено неправильно
}
const user = new BadUser();
user.age = -50; // никто не проверяет!
// ✅ Хорошо: использование методов
class GoodUser {
#age = 0;
setAge(age) {
if (age >= 0 && age <= 150) {
this.#age = age;
}
}
getAge() {
return this.#age;
}
}
Сравнение: до и после инкапсуляции
Без инкапсуляции:
const product = { price: 100, quantity: 5 };
product.price = -50; // упс, отрицательная цена!
product.quantity = 'много'; // упс, не число!
С инкапсуляцией:
class Product {
#price;
#quantity;
constructor(price, quantity) {
this.setPrice(price);
this.setQuantity(quantity);
}
setPrice(price) {
if (price > 0) this.#price = price;
}
setQuantity(quantity) {
if (quantity >= 0 && Number.isInteger(quantity)) this.#quantity = quantity;
}
getTotal() {
return this.#price * this.#quantity;
}
}
const product = new Product(100, 5);
product.setPrice(-50); // ничего не произойдёт
product.setQuantity('много'); // ничего не произойдёт
Инкапсуляция — это не просто техника, это философия программирования, которая создаёт надёжный, предсказуемый и поддерживаемый код. Она является фундаментом качественной архитектуры приложения.