В чем разница между модификаторами доступа public, private и protected?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Модификаторы доступа: public, private, protected
Модификаторы доступа управляют видимостью и доступностью свойств и методов в классах. Это важная часть инкапсуляции в объектно-ориентированном программировании.
1. PUBLIC (публичный)
Определение: Доступен везде - из класса, из подклассов и снаружи класса.
class User {
// Публичное свойство (по умолчанию в JavaScript)
public name: string;
// Публичный метод
public getName(): string {
return this.name;
}
}
const user = new User();
user.name = 'John'; // OK - доступно снаружи
console.log(user.getName()); // OK - доступно снаружи
Характеристики:
- Никаких ограничений
- Может быть переопределен в подклассах
- Видна в IDE автодополнения
- Может быть изменена кто угодно
Когда использовать:
- Публичный API класса
- Данные, которые должны быть доступны снаружи
class Button {
public click() {
console.log('Button clicked');
}
}
const button = new Button();
button.click(); // OK
2. PRIVATE (приватный)
Определение: Доступен ТОЛЬКО внутри самого класса. Недоступен в подклассах и снаружи.
class BankAccount {
private balance: number = 1000; // Приватное свойство
private calculateFee(): number { // Приватный метод
return this.balance * 0.01;
}
public withdraw(amount: number): boolean {
if (amount > this.balance) {
return false;
}
const fee = this.calculateFee();
this.balance -= (amount + fee);
return true;
}
public getBalance(): number {
return this.balance;
}
}
const account = new BankAccount();
account.withdraw(100); // OK
console.log(account.getBalance()); // OK - 890 (1000 - 100 - 10 fee)
account.balance = 10000; // Error! Private field not accessible
account.calculateFee(); // Error! Private method not accessible
Характеристики:
- Жесткие ограничения на доступ
- Не виден в подклассах
- Скрывает внутреннюю реализацию
- Не может быть переопределен подклассом
- Runtime проверка в JavaScript (с #)
В JavaScript (до TypeScript):
// Истинный приватный синтаксис (# - это реальный приватный)
class MyClass {
#privateField = 'private';
#privateMethod() {
return this.#privateField;
}
public getPrivate() {
return this.#privateMethod(); // OK - внутри класса
}
}
const obj = new MyClass();
console.log(obj.#privateField); // Syntax Error!
Когда использовать:
- Внутреннее состояние, которое не должно быть доступно снаружи
- Вспомогательные методы
- Защита инвариантов класса
class Counter {
private count: number = 0;
private validateIncrement(value: number): boolean {
return value > 0 && value < 1000;
}
public increment(value: number): void {
if (this.validateIncrement(value)) {
this.count += value;
}
}
public getCount(): number {
return this.count;
}
}
const counter = new Counter();
counter.increment(5); // OK
console.log(counter.getCount()); // OK
counter.count = 100; // Error!
counter.validateIncrement(5); // Error!
3. PROTECTED (защищенный)
Определение: Доступен в самом классе и в подклассах, но НЕ доступен снаружи.
class Animal {
protected name: string = 'Animal'; // Защищенное свойство
protected makeSound(): string { // Защищенный метод
return 'Some sound';
}
public describe(): string {
return `${this.name} says ${this.makeSound()}`;
}
}
class Dog extends Animal {
public bark(): string {
// OK - можем использовать защищенные члены в подклассе
return `${this.name} says ${this.makeSound()}`;
}
constructor() {
super();
this.name = 'Dog'; // OK - protected в подклассе
}
}
const dog = new Dog();
console.log(dog.describe()); // OK
console.log(dog.bark()); // OK
dog.name; // Error! Protected property
dog.makeSound(); // Error! Protected method
Характеристики:
- Видна в подклассах
- Скрывает от внешнего использования
- Может быть переопределена в подклассах
- Предназначена для наследования
Когда использовать:
- Методы, которые должны быть переопределены подклассами
- Внутреннее состояние для работы с подклассами
class Shape {
protected color: string = 'black';
protected getColor(): string {
return this.color;
}
public abstract draw(): void; // Абстрактный метод
}
class Circle extends Shape {
public draw(): void {
console.log(`Drawing ${this.getColor()} circle`); // OK
}
}
class Rectangle extends Shape {
public draw(): void {
console.log(`Drawing ${this.getColor()} rectangle`); // OK
}
public changeColor(color: string): void {
this.color = color; // OK - protected доступен в подклассе
}
}
const circle = new Circle();
circle.draw(); // OK
circle.color; // Error! Protected
const rect = new Rectangle();
rect.changeColor('blue'); // OK
Таблица сравнения
| Модификатор | В классе | В подклассе | Снаружи |
|---|---|---|---|
| public | Да | Да | Да |
| private | Да | Нет | Нет |
| protected | Да | Да | Нет |
Практические примеры
Пример 1: Инкапсуляция данных
class Rectangle {
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
// Защита от установки неправильных значений
public setWidth(width: number): void {
if (width > 0) {
this.width = width;
}
}
public getWidth(): number {
return this.width;
}
public calculateArea(): number {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 20);
rect.setWidth(15); // OK
console.log(rect.calculateArea()); // 300
rect.width = -5; // Error! Cannot modify
rect.setWidth(-5); // Игнорируется, ширина не меняется
Пример 2: Наследование и protected
class Database {
protected connection: string;
protected connect(): void {
console.log('Connecting...');
}
public executeQuery(sql: string): void {
this.connect(); // OK
console.log(`Executing: ${sql}`);
}
}
class MySQL extends Database {
public customConnect(): void {
this.connect(); // OK - protected доступен в подклассе
}
public setConnection(conn: string): void {
this.connection = conn; // OK - protected
}
}
class PostgreSQL extends Database {
protected connect(): void {
console.log('Connecting to PostgreSQL...'); // Переопределение
}
}
const mysql = new MySQL();
mysql.executeQuery('SELECT *'); // OK
mysql.customConnect(); // OK
mysql.connection; // Error! Protected
mysql.connect(); // Error! Protected
Пример 3: Паттерн Template Method
abstract class ReportGenerator {
protected data: any[] = [];
protected abstract formatData(): string;
protected abstract addHeader(): string;
private validateData(): boolean {
return this.data.length > 0;
}
public generate(): string {
if (!this.validateData()) {
throw new Error('No data');
}
let report = this.addHeader();
report += this.formatData();
return report;
}
}
class CSVReport extends ReportGenerator {
protected formatData(): string {
return this.data.map(item => item.join(',')).join('
');
}
protected addHeader(): string {
return 'id,name,email
';
}
}
class JSONReport extends ReportGenerator {
protected formatData(): string {
return JSON.stringify(this.data, null, 2);
}
protected addHeader(): string {
return ''; // JSON не нужен заголовок
}
}
const csv = new CSVReport();
csv.data = [['1', 'John'], ['2', 'Jane']];
console.log(csv.generate());
Лучшие практики
1. Максимальная инкапсуляция:
// Хорошо - все приватное по умолчанию
class GoodClass {
private field1: string;
private field2: number;
public getData() { }
private validate() { }
}
// Плохо - всё публичное
class BadClass {
field1: string;
field2: number;
getData() { }
validate() { }
}
2. Используй getters/setters с private:
class Age {
private _value: number;
get value(): number {
return this._value;
}
set value(age: number) {
if (age >= 0 && age <= 150) {
this._value = age;
}
}
}
const person = new Age();
person.value = 25; // Выглядит как свойство, но с валидацией
console.log(person.value); // 25
3. Protected для наследования:
// Хорошо - protected для методов, которые переопределяются
class Base {
protected validate(): boolean { return true; }
public process() {
if (this.validate()) { }
}
}
class Extended extends Base {
protected validate(): boolean { return false; } // Переопределение
}
Различие TypeScript vs JavaScript
TypeScript:
class User {
private id: number; // Compile-time проверка
public name: string;
}
const user = new User();
user.id = 5; // TypeScript Error в IDE
JavaScript (с # синтаксисом):
class User {
#id; // Runtime проверка - реально приватный
constructor(id) {
this.#id = id;
}
}
const user = new User(5);
user.#id = 10; // SyntaxError: Private field '#id' must be declared
Заключение
- public - для API класса, то что видят другие
- private - для деталей реализации, скрытого поведения
- protected - для подклассов, когда нужно наследование
Правило thumb: начни с private, открывай только необходимое. Это облегчает рефакторинг и понимание кода.