Классы и ООП

Классы и ООП

Технический фундамент классов в TypeScript

TypeScript усиливает классы типовыми ограничениями:

  • поля и методы получают явные контракты;
  • модификаторы доступа фиксируют границы API класса;
  • readonly защищает инварианты от случайной мутации;
  • наследование проверяется на совместимость сигнатур.

Практически это превращает класс из "контейнера логики" в строго описанный модуль поведения с контролируемым доступом.

Что добавляет TypeScript к классам

JavaScript уже поддерживает классы, но TypeScript делает их типобезопаснее и выразительнее: типы полей и методов, модификаторы доступа, readonly, абстрактные классы. Это помогает строить ООП-модели с четкими контрактами.

Ключевой момент: TypeScript в классах не меняет ООП-идею, но добавляет строгие правила, которые предотвращают ошибки на этапе разработки.

Проверь себя: почему private и readonly полезны в больших командах, где один объект может использоваться в десятках мест?

Типы полей и методов

class User {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  getLabel(): string {
    return `${this.name} (${this.age})`;
  }
}

Смотри, что важно: класс сразу показывает, какие данные хранит и какие методы предоставляет.

Модификаторы доступа

TypeScript поддерживает:

  • public - доступно везде (по умолчанию);
  • private - доступно только внутри класса;
  • protected - внутри класса и наследников.
class Account {
  private balance: number = 0;

  deposit(amount: number): void {
    this.balance += amount;
  }

  getBalance(): number {
    return this.balance;
  }
}

Здесь часто путаются: private не дает напрямую изменить balance снаружи, и это защищает инварианты класса.

private в TypeScript и #private в JavaScript

Важно: модификаторы доступа private/protected в TypeScript работают на этапе компиляции. После компиляции в JavaScript это обычные свойства.

Если нужна реальная приватность в рантайме, в современном JavaScript есть #private-поля.

class A {
  private x = 1; // TS-приватность: проверяется компилятором
}

const a = new A();
// (a as any).x; // в рантайме свойство существует, но TS запрещает доступ без "обхода"

class B {
  #x = 1; // JS-приватность: поле недоступно снаружи даже в рантайме
  getX() {
    return this.#x;
  }
}

const b = new B();
// b.#x; // Ошибка: приватное поле недоступно

Упрощенный синтаксис конструктора

class Product {
  constructor(
    public id: string,
    public title: string,
    private cost: number
  ) {}

  getCost(): number {
    return this.cost;
  }
}

Параметры с модификаторами автоматически становятся полями класса.

implements: класс как реализация контракта

Интерфейс можно использовать как контракт, который класс обязан реализовать.

interface Greeter {
  greet(): string;
}

class UserGreeter implements Greeter {
  constructor(public name: string) {}

  greet(): string {
    return `Hello, ${this.name}`;
  }
}

Смотри, что важно: implements проверяет, что класс соответствует контракту, но сам интерфейс не существует в рантайме.

Наследование и переопределение

class Person {
  constructor(public name: string) {}

  greet(): string {
    return `Hello, ${this.name}`;
  }
}

class Admin extends Person {
  greet(): string {
    return `Admin: ${this.name}`;
  }
}

TypeScript проверяет совместимость сигнатур при переопределении.

Проверь себя: что произойдет, если в наследнике изменить тип возвращаемого значения метода на несовместимый?

Абстрактные классы

Абстрактный класс задает общий контракт, но не предназначен для прямого создания экземпляров.

abstract class Payment {
  abstract pay(amount: number): boolean;
}

class CardPayment extends Payment {
  pay(amount: number): boolean {
    return amount > 0;
  }
}

Это удобно, когда есть общая модель поведения с разными реализациями.

Реальные микро-сценарии

  1. Модель пользователя и ролей.

private защищает внутренние данные, public дает контролируемый API.

  1. Платежные провайдеры.

Абстрактный класс задает обязательный метод pay.

  1. Каталог товаров.

readonly id гарантирует неизменность ключа сущности.

Частые ошибки новичков

  • Делать все поля public по умолчанию.
  • Использовать классы там, где достаточно простых функций и типов.
  • Перегружать классы множеством обязанностей.
  • Пытаться обойти типизацию через as any.

Анти-провал: у каждого класса должна быть одна понятная ответственность и явный публичный контракт.

Что будет, если изменить входные данные

Если конструктор ожидает number, а получает строку, TypeScript остановит это до запуска. Если поле объявлено private, внешняя попытка изменить его даст ошибку компиляции. Это снижает число неявных мутаций и случайных побочных эффектов.

Проверь себя: где лучше контролировать изменение критичного поля - через public доступ или через методы класса?

Краткий итог

  • TypeScript делает ООП-код строже и безопаснее.
  • Типы полей, методов и модификаторы доступа задают четкие границы ответственности.
  • private/protected помогают защищать внутреннее состояние.
  • Абстрактные классы полезны для общих контрактов с разными реализациями.
  • Классы в TS эффективны, когда отражают реальную модель домена, а не используются "по привычке".