На 26-ом уроке мы разберем одну из фундаментальных тем, которая отличает профессиональный код от любительского: модификаторы доступа.
Этот урок посвящен правилам, которые определяют, кто, когда и к каким частям нашего класса имеет доступ. Понимание этих правил дает ключ к созданию безопасных, предсказуемых и легко поддерживаемых приложений. Мы детально разберем три кита, на которых стоит инкапсуляция в TypeScript: public, private и protected.
Объяснение областей видимости. Значение по умолчанию (public)
Представь, что ты проектируешь дом. В этом доме есть гостиная, спальня и сейф. Гостиная это место, куда ты приглашаешь гостей, всё там открыто и доступно. Спальня уже более приватное пространство, заходят туда только члены семьи. А сейф это нечто сугубо личное, доступ к чему имеешь только ты и ещё пара очень доверенных лиц.
В мире объектно-ориентированного программирования наш класс это и есть такой дом. А модификаторы доступа это правила, определяющие, какие «комнаты» (свойства и методы) нашего класса являются «гостиной», какие «спальней», а какие «сейфом».
Область видимости (или доступ) это контекст в коде, из которого можно обратиться к переменной, свойству, методу или классу. TypeScript, как строгий архитектор, позволяет нам очень четко определять эти области с помощью ключевых слов public, private и protected.
Теперь о самом важном и самом распространенном модификаторе public.
Значение по умолчанию public
Если ты до этого урока создавал свойства и методы в классе и не указывал никаких модификаторов, то все они по умолчанию были public. Это означает, что к такому свойству или методу можно получить доступ откуда угодно: изнутри класса, из экземпляра класса, из классов-наследников абсолютно из любого места программы.
Давай рассмотрим простой пример. Создадим класс Car.
class Car { // Не указываем модификатор, значит по умолчанию это public brand: string; model: string; constructor(brand: string, model: string) { this.brand = brand; this.model = model; } // Метод также public по умолчанию getInfo(): string { return `Это автомобиль ${this.brand} ${this.model}`; } } // Создаем экземпляр класса const myCar = new Car('Tesla', 'Model S'); // Мы имеем полный доступ к public-свойствам и методам извне. console.log(myCar.brand); // "Tesla" console.log(myCar.model); // "Model S" console.log(myCar.getInfo()); // "Это автомобиль Tesla Model S"
Как видишь, мы свободно обращаемся ко всему, что объявили в классе. Ключевое слово public можно указывать явно. Это не поменяет поведение, но сделает код более читаемым и явным.
class ExplicitCar { // Явно указываем public public brand: string; public model: string; constructor(brand: string, model: string) { this.brand = brand; this.model = model; } public getInfo(): string { return `Это автомобиль ${this.brand} ${this.model}`; } } const myExplicitCar = new ExplicitCar('Lada', 'Vesta'); console.log(myExplicitCar.brand); // Всё так же работает
Явное указание public это хороший тон. Оно сразу сообщает другому разработчику (или тебе самому через месяц), что ты сознательно сделал этот член класса публичным.
Итак, запомни: все, что помечено как public (явно или по умолчанию), является открытым API твоего класса. Это обещание внешнему миру, что эти свойства и методы стабильны и их можно использовать.
Модификатор private, полная инкапсуляция
Теперь перейдем к «сейфу» в нашем доме-классе. Модификатор private это самый строгий ограничитель доступа.
Свойство или метод, помеченный как private, доступен только внутри того класса, где он был объявлен. Это значит, что к нему нельзя обратиться из экземпляра класса, из класса-наследника или из любого другого места outside класса.
Зачем это нужно? Для полной инкапсуляции. Мы можем скрыть внутреннюю реализацию класса, его «кишки», которые не должны быть никому видны. Мы прячем те данные и вспомогательные методы, которые нужны для внутренней работы класса, но не являются частью его публичного интерфейса.
Классический пример, свойство для хранения пароля или внутреннего состояния.
class User { public username: string; private _password: string; // Приватное свойство constructor(username: string, password: string) { this.username = username; this._password = password; // Сохраняем пароль внутри класса } // Публичный метод для проверки пароля. // Он имеет доступ к private _password. public validatePassword(inputPassword: string): boolean { return inputPassword === this._password; } // А вот так делать НЕЛЬЗЯ! Это нарушит безопасность. // public getPassword(): string { // return this._password; // Ошибка компиляции: нельзя раскрывать приватное свойство // } } const user = new User('Max', 'superSecret123'); console.log(user.username); // "Max" - публичное свойство, доступ есть console.log(user._password); // ОШИБКА! Свойство "_password" является приватным и доступно только внутри класса "User". console.log(user.validatePassword('wrong')); // false console.log(user.validatePassword('superSecret123')); // true
Обрати внимание на два момента:
-
Попытка обратиться к
user._passwordизвне вызывает ошибку компиляции. TypeScript не даст нам скомпилировать такой код, защищая нашу же архитектуру. -
Мы создали публичный метод
validatePassword, который изнутри класса имеет доступ к приватному свойству_passwordи может выполнить с ним нужные операции, не раскрывая его значение.
Соглашение по именованию
Часто приватные свойства предваряют символом подчеркивания _. Это не более чем договоренность между разработчиками. TypeScript не придает этому символу никакого особого значения, для него важен только модификатор private. Но это очень полезное соглашение, которое позволяет просто взглядом отличить приватные члены класса от публичных.
Приватные методы
Точно так же можно делать и приватные методы. Это вспомогательные методы, которые используются внутри класса для решения своих внутренних задач.
class DatabaseConnection { public connect(connectionString: string) { this._validateConnectionString(connectionString); this._establishPhysicalConnection(); this._logSuccess(); } // Приватный метод для внутренней валидации private _validateConnectionString(str: string) { if (!str || str.length === 0) { throw new Error('Connection string is invalid!'); } } // Приватный метод для имитации установки соединения private _establishPhysicalConnection() { console.log('Establishing connection to database...'); // Логика установки соединения... } // Ещё один приватный метод private _logSuccess() { console.log('Connection established successfully!'); } } const db = new DatabaseConnection(); db.connect('my-db://localhost:5432'); // Всё работает // А эти методы вызвать извне НЕЛЬЗЯ: db._validateConnectionString('test'); // Ошибка компиляции! db._establishPhysicalConnection(); // Ошибка компиляции!
Такая архитектура класса DatabaseConnection идеальна. Пользователю класса предоставлен один простой публичный метод connect(). Вся сложная, внутренняя логика (валидация, установка соединения, логирование) надежно спрятана внутри приватных методов. Мы можем как угодно менять реализацию этих методов, не боясь сломать код, который использует наш класс.
Модификатор protected
Мы разобрались с «гостиной» (public) и «сейфом» (private). Осталась «спальня», нечто среднее. Это модификатор protected.
Свойство или метод, помеченный как protected, доступен внутри класса, где объявлен, а также внутри всех классов-наследников (дочерних классов). При этом из экземпляра класса обратиться к protected-члену по-прежнему нельзя.
Это инструмент для построения иерархий классов, когда базовый класс содержит некоторую общую логику и данные, которые должны быть доступны его потомкам, но скрыты от внешнего мира.
Давай рассмотрим пример с базовым классом Vehicle (транспортное средство) и дочерним классом Car.
class Vehicle { // Protected свойство. Не public, но и не private. protected brand: string; constructor(brand: string) { this.brand = brand; } // Protected метод. protected startEngine(): void { console.log(`Двигатель ${this.brand} запущен!`); } // Public метод, который использует protected метод. public drive(): void { this.startEngine(); console.log(`${this.brand} начал движение.`); } } class Car extends Vehicle { private model: string; constructor(brand: string, model: string) { super(brand); // Вызываем конструктор родительского класса this.model = model; } // Дочерний класс имеет доступ к protected свойству и методу родителя. public getFullName(): string { // Мы можем обратиться к protected brand здесь! return `${this.brand} ${this.model}`; } public makeSound(): void { // Мы можем вызвать protected метод родительского класса! this.startEngine(); console.log('Вжжжжжж!'); } } const myVehicle = new Vehicle('SomeVehicle'); const myCar = new Car('Toyota', 'Camry'); // Public методы доступны отовсюду: myVehicle.drive(); myCar.drive(); myCar.getFullName(); // "Toyota Camry" myCar.makeSound(); // А вот protected члены извне - НЕДОСТУПНЫ: console.log(myVehicle.brand); // Ошибка: protected, доступ только внутри иерархии myVehicle.startEngine(); // Ошибка: protected console.log(myCar.brand); // Ошибка: protected myCar.startEngine(); // Ошибка: protected
Это идеальный пример использования protected. Класс Vehicle говорит: «Внутренний механизм запуска двигателя (startEngine) и название бренда (brand) это детали реализации моей семьи (моей и моих потомков). Внешний мир не должен о них знать или напрямую ими управлять. Внешний мир может использовать только открытый метод drive(), а я уже сам решу, как внутри него запустить двигатель».
Класс-наследник Car является частью этой «семьи», поэтому он имеет полный доступ ко всем protected-ресурсам родителя.
Сравнительная таблица модификаторов доступа
Давай соберем все знания в одну простую таблицу для наглядности.
| Модификатор | Доступ внутри класса | Доступ в дочерних классах | Доступ из экземпляра класса |
|---|---|---|---|
public |
✅ | ✅ | ✅ |
protected |
✅ | ✅ | ❌ |
private |
✅ | ❌ | ❌ |
Эта таблица, твоя шпаргалка. Пользуйся ею, когда сомневаешься, какой модификатор выбрать.
Практические примеры и задачи
Давай закрепим знания на реальных кейсах.
Задача 1: Банковский счет
Создай класс BankAccount, который инкапсулирует логику работы с банковским счетом.
-
Свойство
private _balance: numberхранит текущий баланс. Нельзя позволять менять его напрямую. -
Конструктор должен инициализировать счет с определенной суммой (по умолчанию 0).
-
Публичный метод
public deposit(amount: number): voidдля пополнения счета. -
Публичный метод
public withdraw(amount: number): voidдля снятия денег. Должна быть проверка, что средств достаточно. -
Публичный метод
public getBalance(): numberединственный безопасный способ узнать баланс извне.
Решение:
class BankAccount { private _balance: number; constructor(initialBalance: number = 0) { this._balance = initialBalance; } public deposit(amount: number): void { if (amount <= 0) { throw new Error('Сумма для пополнения должна быть положительной'); } this._balance += amount; console.log(`Счет пополнен на ${amount}. Текущий баланс: ${this._balance}`); } public withdraw(amount: number): void { if (amount <= 0) { throw new Error('Сумма для снятия должна быть положительной'); } if (this._balance < amount) { throw new Error('Недостаточно средств на счете'); } this._balance -= amount; console.log(`Со счета снято ${amount}. Текущий баланс: ${this._balance}`); } public getBalance(): number { return this._balance; } } const myAccount = new BankAccount(1000); myAccount.deposit(500); // "Счет пополнен на 500. Текущий баланс: 1500" myAccount.withdraw(200); // "Со счета снято 200. Текущий баланс: 1300" // myAccount._balance = 1000000; // Ошибка компиляции! Нельзя изменить баланс в обход методов. console.log(myAccount.getBalance()); // 1300
Задача 2: Иерархия сотрудников
Создай базовый класс Employee (Сотрудник).
-
У него будут
protectedсвойстваname(имя) иsalary(зарплата). -
publicметодgetInfo(), который возвращает информацию о сотруднике. -
Создай дочерний класс
Manager(Менеджер), который добавляет новое свойствоprivate _department(отдел). -
Переопредели в классе
ManagerметодgetInfo(), чтобы он включал информацию об отделе. Используйprotectedсвойства родителя.
Решение:
class Employee { protected name: string; protected salary: number; constructor(name: string, salary: number) { this.name = name; this.salary = salary; } public getInfo(): string { return `Сотрудник ${this.name}, зарплата: ${this.salary}`; } } class Manager extends Employee { private _department: string; constructor(name: string, salary: number, department: string) { super(name, salary); this._department = department; } // Имеем доступ к protected name и salary из родительского класса! public getInfo(): string { return `Менеджер ${this.name}, отдел: ${this._department}, зарплата: ${this.salary}`; } } const employee = new Employee('Иван', 50000); console.log(employee.getInfo()); // "Сотрудник Иван, зарплата: 50000" const manager = new Manager('Мария', 80000, 'Маркетинг'); console.log(manager.getInfo()); // "Менеджер Мария, отдел: Маркетинг, зарплата: 80000" // employee.name; // Ошибка: protected, нельзя обратиться извне // manager.name; // Ошибка: protected, нельзя обратиться извне
Важные нюансы и особенности
Модификаторы доступа это фича TypeScript. Важно понимать, что она существует только на этапе компиляции. JavaScript, в который компилируется наш TypeScript-код, не имеет такой концепции на уровне языка (по крайней мере, до недавних версий, но об этом ниже).
Когда TypeScript компилирует код с private или protected, он просто убирает эти модификаторы и в выходном JS-файле все свойства становятся обычными публичными полями. Вся проверка типов и доступов происходит до момента запуска кода, в твоем редакторе и в терминале при запуске tsc.
Это означает, что обойти защиту TypeScript’а можно, если очень захотеть (например, используя any и грубое приведение типов). Но цель этих модификаторов, не создать неприступную крепость, а помочь тебе самому architect правильные и надежные взаимодействия между частями твоего кода, поймать ошибки на самом раннем этапе, этапе написания кода.
Private Fields в современном JavaScript
Современный стандарт JavaScript (ECMAScript) ввел поддержку приватных полей на самом языке. Синтаксис отличается: вместо слова private используется символ # перед именем поля.
TypeScript позволяет использовать этот синтаксис, если тыtarget’ишь современные версии JavaScript.
class ModernClass { #truePrivateSecret: string; // Приватное поле на уровне JavaScript constructor(secret: string) { this.#truePrivateSecret = secret; } getSecret(): string { return this.#truePrivateSecret; // Доступ внутри класса есть } } const instance = new ModernClass('my secret'); console.log(instance.getSecret()); // "my secret" console.log(instance.#truePrivateSecret); // Ошибка на этапе компиляции TypeScript И на этапе выполнения JavaScript!
Эти поля являются приватными не только на этапе компиляции, но и в скомпилированном JavaScript-коде. Это настоящая, «жесткая» инкапсуляция.
Для большинства проектов на TypeScript хватает классических private/protected модификаторов. Но если ты хочешь максимальной защиты и пишешь под современные браузеры или Node.js последних версий, можно использовать и #-синтаксис.
Итоги урока 26
Сегодня мы прошли один из важнейших рубежей в освоении объектно-ориентированного программирования в TypeScript. Давай резюмируем:
-
public: публичный доступ отовсюду. Является модификатором по умолчанию. Используется для определения публичного API класса. -
private: приватный доступ только внутри объявленного класса. Используется для полной инкапсуляции внутреннего состояния и вспомогательных методов. -
protected: защищенный доступ внутри класса и его потомков. Используется для построения иерархий классов, предоставляя потомкам доступ к общим ресурсам, скрытым от внешнего мира.
Правильное применение этих модификаторов является признаком зрелого разработчика. Это позволяет:
-
Создавать надежные и безопасные классы.
-
Четко определять контракты взаимодействия (что можно использовать снаружи, а что нет).
-
Легко вносить изменения во внутреннюю реализацию классов, не боясь сломать код, который их использует.
-
Строить сложные и гибкие иерархии наследования.
Обязательно потренируйся использовать их в своем коде. Сначала будет непривычно постоянно думать о том, какой модификатор выбрать, но очень скоро это войдет в привычку и твой код станет на порядок лучше.
Не забывай, что все уроки курса ты можешь найти по ссылке ниже. Увидимся на следующем уроке, где мы продолжим углубляться в возможности TypeScript!
Полный курс с уроками по TypeScript для начинающих: https://max-gabov.ru/typescript-dlya-nachinaushih
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


