← Назад к вопросам

В чем разница между модификаторами доступа public, private и protected?

2.0 Middle🔥 151 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Модификаторы доступа: 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, открывай только необходимое. Это облегчает рефакторинг и понимание кода.