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

Инкапсуляция означает удержание чего-то в изоляции — если поместить что-то в капсулу, внешнему миру недоступно его внутреннее состояние. В объектно-ориентированном программировании инкапсуляция помогает держать сложный код управляемым и предсказуемым.
Зачем нужны классы
Представьте приложение для контактного зоопарка с огромной кодовой базой. В нём есть важный объект, центральный для логики, например animal. Что если каждый компонент программы мог бы напрямую читать и изменять этот объект?
Свободный доступ приведёт к путанице. Если поросёнок использует animal для определения своих свойств, тогда animal приобретёт атрибуты поросёнка. Потом коза использует тот же объект — и вы получите неожиданные мутации.
Пример на JavaScript/TypeScript, иллюстрирующий проблему:
var animal = {name: "piglet", legs: 4, color: "pink", decoration: "snout"}
animal.name = "goat"
animal.decoration = "horns"
В результате у вас окажутся розовые козы и поросята с рогами. В больших проектах отследить, какой именно участок кода изменил объект, может занять сотни часов. После этого придётся писать дополнительные заглушки и проверки, чтобы ограничить побочные эффекты.
Решение — определять объекты через классы. Любая часть кода может создать экземпляр класса. Инстанцирование гарантирует, что у каждого объекта свои собственные свойства и они не будут вмешиваться друг в друга.
Классов недостаточно — переменные объекта тоже должны быть инкапсулированы
Создадим класс, описывающий наших животных:
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.color = "black";
bunny.legs = 8;
Паучьи кролики — не лучшая идея. Это всё та же проблема: объекты можно изменить извне. Решение — сделать поля приватными и предоставить контролируемый доступ через геттеры и сеттеры.
Вот как выглядит класс с приватными полями и контролем для количества ног:
class Animal {
private _name: string;
private _legs: number;
private _color: string;
private _decoration: string;
constructor(name: string, legs: number, color: string, decoration: string) {
this._name = name;
this._legs = legs;
this._color = color;
this._decoration = decoration;
}
get legs() {
return this._legs;
}
set legs(legCount: number) {
if(legCount > 1 && legCount < 5) {
this._legs = legCount;
}
}
}
Геттеры и сеттеры — это функции, которые читают и изменяют внутренние переменные в контролируемой форме. Сеттеры позволяют наложить ограничения на значения, а геттеры — модифицировать или форматировать данные при выдаче.
Практическое резюме и задания для закрепления
Вот финальный список задач и резюме того, что вы должны сделать:
- Добавьте геттеры и сеттеры для остальных переменных (_name, _color, _decoration).
- Верните имя животного как span-тег: llama. Это упражнение показывает, что геттер может форматировать вывод.
- Измените decoration так, чтобы он поддерживал несколько украшений. Создайте соответствующие геттер и сеттер для массива украшений.
Важно: избегайте глобальных переменных. Если необходимо делиться состоянием между объектами, используйте статические поля класса или отдельные сервисы с чётким API.
Полезные шаблоны и приёмы
Приведу дополнительные шаблоны и практические советы, которые пригодятся при работе с инкапсуляцией в TypeScript.
Приватные поля и readonly
- Используйте private или private readonly, если поле не должно меняться после создания.
- В новых версиях TypeScript можно использовать синтаксис приватных полей с #, он обеспечивает приватность на уровне рантайма (ESNext).
Пример с readonly и массивом украшений:
class AnimalV2 {
private _name: string;
private _legs: number;
private _color: string;
private _decorations: string[];
constructor(name: string, legs: number, color: string, decorations: string[]) {
this._name = name;
this._legs = legs;
this._color = color;
this._decorations = decorations;
}
get name(): string {
return `${this._name}`;
}
get decorations(): string[] {
return [...this._decorations]; // возвращаем копию
}
set decorations(items: string[]) {
// валидация: максимум 5 украшений
if (items.length <= 5) {
this._decorations = [...items];
}
}
}Обратите внимание: геттер decorations возвращает копию массива, чтобы внешний код не мог напрямую мутировать внутренний массив.
Модель мышления и эвристики
- Правило одного владельца состояния: каждая сущность должна владеть своим состоянием; изменение состояния другой сущности должно идти через явные методы или события.
- Минимизация привилегий: давайте доступ только тем частям кода, которым он действительно нужен.
- Явная валидация: ставьте проверки в сеттерах и в конструкторе.
Когда инкапсуляция не решит проблему
- Если у вас глобальные однострочные скрипты или утилиты, классы могут усложнить простую логику.
- Для некоторых архитектур (функциональный стиль, data-oriented) композиция и чистые функции предпочтительнее классов.
- В системах с высокой частотой обновлений состояние можно держать в отдельных стореджах/редьюсерах, а не в экземплярах классов.
Контрпример: если вам нужно очень часто копировать данные между миллионами простых структур, накладные расходы методов доступа могут быть заметны; в таких случаях лучше использовать простые структуры данных и чистые функции.
Альтернативы классам
- Композиция + фабрики: функции, которые создают объекты с замыканиями (closures) для приватности.
- Модули (namespaces) и замыкания: переменные модуля недоступны извне, пока вы не экспортируете API.
- Immutable-объекты: вместо изменения состояния создавайте новые объекты.
Простой пример замыкания как альтернативы:
function createAnimal(name, legs) {
let _name = name;
let _legs = legs;
return {
getName() { return _name; },
setLegs(n) { if (n > 1 && n < 5) _legs = n; },
getLegs() { return _legs; }
};
}Миграция старой кодовой базы — мини-методология
- Найдите самые изменяемые объекты (частые глобальные мутации).
- Вокруг них создайте классы/обёртки с приватными полями и контролируемым API.
- Пошагово заменяйте прямые обращения на вызовы методов класса.
- Покройте изменения тестами: юнит-тесты для геттеров/сеттеров и интеграционные тесты на сценарии.
- Рефакторьте и удаляйте глобальные состояния по мере уверенности в тестах.
Критерии приёмки
- Все прямые обращения к ранее глобальным полям заменены методами/геттерами.
- Сценарии, которые ранее ломали объекты (например, некорректное число ног), теперь предотвращаются сеттерами.
- Появились юнит-тесты, подтверждающие валидацию в сеттерах.
Роль-based checklists
Developer:
- Внедрять приватные поля и сеттеры для критичных состояний.
- Не возвращать внутренние ссылки на изменяемые объекты (возвращайте копии).
Code reviewer:
- Проверять, что геттеры/сеттеры валидируют данные.
- Убедиться, что нет прямых мутаций приватных полей извне.
Architect:
- Решать, где использовать классы, а где — функциональный стиль.
- Определять правила владения состоянием и границы модулей.
Небольшой глоссарий в одну строку
Инкапсуляция — изоляция внутреннего состояния объекта и предоставление контролируемого API для доступа.
Краткое повторение
- Используйте классы и приватные поля, чтобы избежать неожиданных мутаций.
- Геттеры и сеттеры дают контроль и позволяют валидировать и форматировать данные.
- Альтернативы: замыкания, модули и неизменяемые структуры.
Примечание: переводите существующие глобальные состояния в управляемые сервисы постепенно и с тестами.
Сводка и дальнейшие шаги
- Начните с ключевых объектов, инкапсулируйте поля, добавьте проверки.
- Для коллекций возвращайте копии, чтобы не раскрывать внутреннее состояние.
- Обсуждайте с командой архитектурные решения и документируйте правила владения состоянием.
Похожие материалы
Встроенные команды оболочки в Linux — как проверить
Как настроить пользовательские профили Fujifilm
Настройка рабочего стола Windows 8
Как делать скриншоты в Windows 11 и 10
Сделать iPhone похожим на Android — руководство