Инкапсуляция в TypeScript: классы, геттеры и сеттеры

Инкапсуляция означает изоляцию данных: если что-то помещено в «капсулу», внешний код не может получить к этому прямой доступ. В объектно-ориентированном программировании это помогает удерживать сложность под контролем и предотвращать непреднамеренные изменения состояния.
Что такое инкапсуляция (одно предложение)
Инкапсуляция — это практика скрывать внутренние данные и предоставлять контролируемый интерфейс для их чтения и изменения.
Почему нужны классы
Представьте приложение «контактный зоопарк» с сотнями тысяч строк кода. Есть центральный объект animal. Что произойдет, если любой кусок программы сможет напрямую менять поля animal?
Бесконтрольный доступ приведет к хаосу. Если поросёнок назначит параметры animal, то у объекта появятся поля поросёнка. Если затем коза тоже изменит animal, вы получите странные смешения: розовые козы и поросята с рогами.
Вот аналогичная ситуация в JS/TS (упрощённо):
var animal = { name: 'piglet', legs: 4, color: 'pink', decoration: 'snout' };
animal.name = 'goat';
animal.decoration = 'horns';Если код большой, поиск и исправление всех мест, где кто-то мутирует общие объекты, займёт часы и добавит ещё больше «спагетти»-кода для обхода проблем. Лучшее решение — определять объекты через классы и создавать экземпляры (instantiation). Каждый экземпляр получает собственные свойства и не мешает другим.
Классы — это не всё: поля объектов тоже нужно инкапсулировать
Создадим класс Animal в TypeScript, который задаёт структуру сущности:
class Animal {
name: string;
legs: number;
color: string;
decoration: string;
constructor(name: string, legs: number, color: string, decoration: string) {
this.name = name;
this.legs = legs;
this.color = color;
this.decoration = decoration;
}
}
let babyDuck = new Animal('baby duck', 2, 'yellow', 'beak');
let bunny = new Animal('bunny', 4, 'gray', 'floppy ears');Пока всё хорошо — каждый объект отдельный. Но что если уставший программист по ошибке поменял объект bunny?
bunny.color = 'black';
bunny.legs = 8; // оopsКролики‑пауки — нежелательный эффект. Чтобы этого избежать, поля класса нужно сделать приватными и управлять доступом через геттеры и сеттеры.
Приватные поля и контролируемый доступ
В TypeScript приватные поля обозначают ключевым словом private. Вместо прямого доступа даём публичные геттеры/сеттеры с валидацией:
class Animal {
private _name: string;
private _legs: number;
private _color: string;
private _decoration: string | string[];
constructor(name: string, legs: number, color: string, decoration: string | string[]) {
this._name = name;
this._legs = legs;
this._color = color;
this._decoration = decoration;
}
get legs() {
return this._legs;
}
set legs(legCount: number) {
if (legCount > 0 && legCount <= 8) {
this._legs = legCount;
} else {
// проигнорировать некорректное значение или бросить ошибку
}
}
get name() {
return this._name;
}
set name(value: string) {
if (value && value.length < 50) {
this._name = value;
}
}
get color() {
return this._color;
}
set color(value: string) {
// можно нормализовать цвет: lowerCase, валидация и т.д.
this._color = value;
}
get decoration() {
return Array.isArray(this._decoration) ? this._decoration.slice() : [this._decoration];
}
set decoration(value: string | string[]) {
if (typeof value === 'string') {
this._decoration = [value];
} else {
this._decoration = value;
}
}
}Ключевые моменты:
- Приватные поля защищают внутреннее состояние.
- Геттеры возвращают безопасную копию (slice) для массивов, чтобы внешний код не мутировал внутренние структуры.
- Сеттеры выполняют валидацию и нормализацию.
Практические советы и шаблон действий (mini‑методология)
- Начните с определения минимально необходимого публичного API класса.
- Сделайте все внутренние поля приватными.
- Добавьте геттеры/сеттеры только для тех полей, которые должны быть доступны извне.
- В сеттерах выполняйте валидацию и нормализацию данных.
- Для коллекций возвращайте копии вместо прямых ссылок.
- Используйте static поля только для данных, общих для всех экземпляров.
- Покройте поведение тестами: граничные и ошибочные значения.
Когда инкапсуляция может не быть оптимальной (контрпримеры)
- Небольшие скрипты или прототипы, где overhead от классов мешает простоте. В таком случае простые функции и объекты могут быть приемлемы.
- Высокопроизводительный код, где создание копий массивов в геттерах становится узким местом. Тогда нужно документировать ограничения и предоставлять методы-операции, а не копии.
Альтернативы и дополнения
- Функциональный подход: неизменяемые объекты и pure-функции вместо геттеров/сеттеров.
- Компонентный подход (например, ECS) для игр, где данные и поведение отделены.
- Использование TypeScript readonly для обеспечения неизменности после создания.
Ролевые чек‑листы при ревью кода
Для разработчика:
- Приватны ли поля, не предназначенные для внешнего доступа?
- Есть ли валидация в сеттерах?
- Возвращаются ли копии коллекций?
Для ревьюера:
- Нет ли прямых мутаций объектов из других модулей?
- Хорошо ли покрыты тестами граничные случаи?
Для архитектора:
- Следует ли использовать статические поля для данных уровня приложения?
- Не нарушает ли текущая модель инкапсуляции ограничений производительности?
Примеры использования статических/классных переменных
Если нужно разделять данные между всеми экземплярами класса, используйте static:
class Zoo {
static totalAnimals = 0;
constructor() {
Zoo.totalAnimals += 1;
}
}Но помните: static — это глобальное состояние в масштабе класса. Его нужно документировать и ограничивать.
Ментальные модели и эвристики
- Подумайте о каждом объекте как об отдельном «ящике» с крышкой: кто может её открыть и как.
- Правило трёх: если поле требует контроля доступа в трёх местах кода — сделайте его приватным и вынесите проверку в метод или сеттер.
Критерии приёмки
- Все внутренние поля, не предназначенные для публичного API, объявлены private.
- Сеттеры выполняют базовую валидацию и не допускают «нелогичных» состояний.
- Тесты проверяют корректные и некорректные значения для сеттеров и геттеров.
Тестовые случаи (основные)
- Попытка установить legs = -1 должна быть проигнорирована или вызвать ошибку.
- Установка decoration строкой и массивом должна приводить к корректному внутреннему виду.
- Геттер decoration должен возвращать копию массива: изменение результата не должно менять внутреннее поле.
Краткое резюме
- Инкапсуляция предотвращает нежелательные мутации и уменьшает связность.
- Используйте private поля и геттеры/сеттеры для контроля доступа и валидации.
- Возвращайте копии коллекций и документируйте static-поля.
Важно: инкапсуляция не панацея. Она снижает риск ошибок, но требует дисциплины и хорошего тестирования.
Часто задаваемые вопросы
Что такое геттер и сеттер?
Геттер — это метод, который возвращает значение поля; сеттер — метод, который изменяет поле с возможной валидацией.
Нужно ли всегда использовать геттеры/сеттеры?
Нет. Для простых DTO или immutable-объектов геттеры/сеттеры не всегда нужны. Для сущностей с бизнес-логикой они практически обязательны.
Похожие материалы
Отключить фоновые приложения в Windows 11
Совместная работа в VS Code с Live Share
Как вставить и найти символы в Google Docs
Google Play Music Desktop Player — обзор клиента
Как поставить GIF в фон Google