Продолжая наше погружение в TypeScript, мы переходим к рассмотрению классов — мощного инструмента объектно-ориентированного программирования. В этой статье мы изучим основы создания классов в TypeScript, их свойства, методы, конструкторы, наследование, абстрактные классы и интерфейсы.

Создание класса в TypeScript

Для лучшего понимания давайте рассмотрим пример использования классов в контексте приложения для списка покупок.

class GroceryItem {
  name: string;
  quantity: number;
  isPurchased: boolean;

  constructor(name: string, quantity: number) {
    this.name = name;
    this.quantity = quantity;
    this.isPurchased = false;
  }

  purchase() {
    this.isPurchased = true;
  }
}

// Создаем экземпляры класса GroceryItem
const milk = new GroceryItem('Молоко', 1);
const bread = new GroceryItem('Хлеб', 2);

// Покупаем продукты
milk.purchase();

console.log(milk); // Выведет: GroceryItem { name: 'Молоко', quantity: 1, isPurchased: true }
console.log(bread); // Выведет: GroceryItem { name: 'Хлеб', quantity: 2, isPurchased: false }

В этом примере мы создали класс GroceryItem, который представляет собой элемент списка покупок. Класс GroceryItem определен с тремя свойствами:

  • name имеет тип string, что означает, что ожидается строковое значение (название продукта).
  • quantity имеет тип number, что указывает на числовое значение (количество продукта).
  • isPurchased имеет тип boolean, который представляет логическое значение (true или false), указывающее, куплен ли продукт.

Использование заранее определенных типов данных в данном примере класса GroceryItem в TypeScript позволяет точно определить тип каждого свойства, что предотвращает непреднамеренные ошибки при работе с данными, такие как неправильное присваивание значений или вызов недопустимых методов. Это обеспечивает более надежный код и упрощает его поддержку и развитие.

Наследование в TypeScript

Наследование позволяет создавать новый класс на основе существующего, заимствуя его свойства и методы. Давайте рассмотрим пример наследования в контексте списка покупок.

class GroceryItem {
  constructor(public name: string, public quantity: number, public isPurchased: boolean = false) {}

  purchase() {
    this.isPurchased = true;
  }
}

class SpecialGroceryItem extends GroceryItem {
  constructor(name: string, quantity: number, public discount: number) {
    super(name, quantity);
  }

  applyDiscount() {
    const discountedPrice = this.quantity * this.discount;
    console.log(`Скидка: ${discountedPrice}`);
  }
}

// Создаем экземпляр класса SpecialGroceryItem
const specialItem = new SpecialGroceryItem('Специальный товар', 3, 0.2);
specialItem.purchase();
specialItem.applyDiscount();

console.log(specialItem); // Выведет: SpecialGroceryItem { name: 'Специальный товар', quantity: 3, isPurchased: true, discount: 0.2 }

В этом примере мы создали класс SpecialGroceryItem, который наследует свойства и методы от класса GroceryItem. Класс SpecialGroceryItem добавляет новое свойство discount и метод applyDiscount(), который применяет скидку к товару.

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

Модификаторы доступа позволяют управлять доступом к свойствам и методам классов. TypeScript поддерживает три типа модификаторов доступа:

  • public: Публичные члены класса доступны из любого места в программе. Это значит, что они могут быть доступны как внутри самого класса, так и вне его.
  • private: Приватные члены класса доступны только внутри самого класса. Они не могут быть доступны извне класса или его наследников.
  • protected: Защищенные члены класса доступны внутри самого класса и его подклассов (наследников), но не извне класса.

Модификатор public является значением по умолчанию для членов класса в TypeScript. Это означает, что если член класса не имеет явно указанного модификатора доступа, то он автоматически считается public, что делает его доступным из любой точки программы без дополнительных усилий.

Рассмотрим пример для модификатора private:

class GroceryList {
  private items: GroceryItem[] = [];

  addItem(item: GroceryItem) {
    this.items.push(item);
  }

  printList() {
    this.items.forEach(item => console.log(item.name));
  }
}

const groceryList = new GroceryList();
groceryList.addItem(new GroceryItem('Яблоки', 5));
groceryList.printList(); // Выведет: Яблоки

В этом примере мы использовали модификатор доступа private для свойства items класса GroceryList, что означает, что оно доступно только внутри самого класса. Это обеспечивает защиту данных и предотвращает их непосредственное изменение извне.

Пример для модификатора protected:

class GroceryList {
  protected items: GroceryItem[] = [];

  addItem(item: GroceryItem) {
    this.items.push(item);
  }

  printList() {
    this.items.forEach(item => console.log(item.name));
  }
}

class SpecialGroceryList extends GroceryList {
  applyDiscount() {
    this.items.forEach(item => {
      // Класс SpecialGroceryList имеет доступ к защищенному свойству items
      console.log(`Применение скидки к: ${item.name}`);
    });
  }
}

const groceryList = new SpecialGroceryList();
groceryList.addItem(new GroceryItem('Яблоки', 5));
groceryList.printList(); // Выведет: Яблоки
groceryList.applyDiscount(); // Применение скидки к: Яблоки

В этом примере свойство items класса GroceryList помечено модификатором protected, что означает, что оно доступно как внутри самого класса, так и в его подклассах (в данном случае SpecialGroceryList). Таким образом, подкласс SpecialGroceryList может использовать метод printList() для вывода списка продуктов и даже обращаться к защищенному свойству items для применения каких-либо дополнительных операций, например, применения скидки.

Статические свойства и методы в TypeScript

Статические свойства и методы предоставляют возможность создавать свойства и методы, которые принадлежат самому классу, а не его экземплярам. Это позволяет использовать класс как пространство имен для функциональности, которая не зависит от конкретных экземпляров.

Давайте рассмотрим пример статического метода в классе GroceryItem, который считает общее количество всех продуктов в списке.

class GroceryItem {
  static totalItems: number = 0;

  constructor(public name: string, public quantity: number, public isPurchased: boolean = false) {
    GroceryItem.totalItems++;
  }

  purchase() {
    this.isPurchased = true;
  }

  static getTotalItems() {
    return GroceryItem.totalItems;
  }
}

// Создаем экземпляры класса GroceryItem
const apple = new GroceryItem('Яблоки', 3);
const banana = new GroceryItem('Бананы', 2);

console.log(GroceryItem.getTotalItems()); // Выведет: 2

В этом примере мы создали статическое свойство totalItems, которое хранит общее количество всех продуктов в списке. Мы также создали статический метод getTotalItems(), который возвращает это общее количество.

Статические свойства и методы могут быть использованы без создания экземпляра класса, что делает их полезными для функциональности, которая не зависит от конкретных объектов.

Применение статических методов и свойств

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

class GroceryItem {
  static totalItems: number = 0;
  static items: GroceryItem[] = [];

  constructor(public name: string, public quantity: number, public isPurchased: boolean = false) {
    GroceryItem.totalItems++;
    GroceryItem.items.push(this);
  }

  purchase() {
    this.isPurchased = true;
  }

  static getTotalItems() {
    return GroceryItem.totalItems;
  }

  static printAllItems() {
    GroceryItem.items.forEach(item => console.log(item.name));
  }
}

// Создаем экземпляры класса GroceryItem
const orange = new GroceryItem('Апельсины', 4);
const pear = new GroceryItem('Груши', 5);

console.log(GroceryItem.getTotalItems()); // Выведет: 2
GroceryItem.printAllItems(); // Выведет: Апельсины, Груши

В этом примере мы используем статические свойства totalItems и items, чтобы отслеживать общее количество продуктов и хранить список всех продуктов в списке. Мы также создали статический метод printAllItems(), который выводит все названия продуктов.

Статические свойства и методы предоставляют удобный способ организации функциональности, которая не привязана к конкретным экземплярам класса. Это делает классы в TypeScript еще более гибкими и мощными инструментами для создания структурированного и эффективного кода.