Сегодня мы собираемся освежить в памяти классы TypeScript, интерфейсы, наследование и другие концепции объектно-ориентированного программирования (ООП).
Хотя TypeScript и JavaScript могут быть не первыми языками, о которых вы думаете, когда дело доходит до объектно-ориентированного программирования, вы будете удивлены, узнав, насколько широко поддерживается создание сложных, надежных компонентов в объектно-ориентированном стиле. В последних версиях TypeScript и JavaScript были внесены изменения в их синтаксис, чтобы упростить реализацию ООП.
По многочисленным просьбам сегодня мы сосредоточимся именно на концепциях ООП в TypeScript. Мы начнем с краткого изложения того, что такое классы, а затем перейдем к таким понятиям, как инкапсуляция, наследование классов, интерфейсы и многое другое!
К концу у вас должно быть базовое понимание того, как реализовать различные концепции ООП в TypeScript.
- Что такое классы?
- Объявить класс TypeScript
- Инкапсуляция с классами
- Объекты класса
- Конструкторы классов
- Наследование классов
- Модификаторы доступа
- Публичные модификаторы
- Частные модификаторы
- Защищенные модификаторы
- Наследование с модификаторами доступа
- Интерфейсы и псевдонимы типов
- Псевдонимы типов
- Интерфейсы
- Интерфейсы и наследование
Что такое классы?
До выпуска ECMASCript 6 (ES6) JavaScript был преимущественно функциональным языком программирования, в котором наследование основывалось на прототипах. Когда в 2015 году был введен синтаксис для поддержки классов, TypeScript быстро адаптировался, чтобы использовать преимущества объектно-ориентированных методов, таких как инкапсуляция и абстракция.
Классы TypeScript и JavaScript состоят из следующего:
- Конструкторы
- Характеристики
- Методы
Классы обеспечивают фундаментальную структуру для создания многократно используемых компонентов в JavaScript. Они представляют собой абстракцию в языках объектно-ориентированного программирования (ООП), используемую для определения объектов и передачи свойств и функций другим классам и объектам.
Объекты — это структуры данных, созданные путем инкапсуляции данных, и методы, работающие с этими данными. Таким образом, вы можете думать о классах JavaScript не как об объектах в прямом смысле, а как о чертежах объектов.
Примечание. TypeScript полностью поддерживает синтаксис класса, представленный в 2015 году с выпуском ECMAScript 6 (ES6).
Объявить класс TypeScript
Объявления классов используются для определения класса с использованием classключевого слова вместе с именем класса и фигурными скобками ’{}’.
class Fruit {// this is an empty class}
Выражения класса — это еще один способ определить класс, но они могут быть именованными или безымянными.
let Fruit = class {// this class is unnamed}let Fruit = class edible_fruits {// this class is named}
Вы можете получить доступ к выражениям именованного класса, используя name ключевое слово.
let Fruit = class edible_fruits {// this class is named}console.log(Fruit.name);// returns «edible_fruits»
Инкапсуляция с классами
Ключевой концепцией объектно-ориентированного программирования является инкапсуляция. Инкапсуляция влечет за собой ограничение доступа к состоянию объекта путем помещения данных и методов в один блок. Ограничение доступа к определенным данным или компонентам может быть полезно для предотвращения вызова приватных методов из внешнего кода внутри определенного класса.
TypeScript упрощает инкапсуляцию, заключая данные и связанные с ними методы в класс.
Например, если у вас есть «класс Student» с двумя элементами данных и методом, вы можете инкапсулировать их, используя следующий синтаксис:
class Student {name: string=»roll: number = 0getRoll(): number{return this.roll}}
Объекты класса
В JavaScript объекты — это переменные, которые могут содержать одно значение.
let fruit_one = «Apple»;
Объекты также являются переменными, но вместо одного значения им можно присвоить несколько значений, записанных в виде пар ключ: значение. Набор этих пар ключ: значение составляют различные свойства объекта, которому они принадлежат.
const fruits = {name: «Apple»,color: «red»,variety: «Fuji»};
Чтобы получить доступ к членам класса (данным или методам), мы должны создать экземпляр их объекта.
В следующем примере есть два класса Student, и School. Используя объект класса School, мы можем попытаться получить доступ к члену данных класса «Студент» с помощью uni.roll.
Это вернет предупреждение о том, что требуемые данные не существуют в классе School. Вместо этого мы можем исправить код, комментируя uni.rollи раскомментируя student.roll.
Попробуйте сами!
// Student classclass Student {name: string = »roll: number = 0getRoll(): number {return this.roll}}// School classclass School {name: string =»location: string = »}// Create objects of each class.const student = new Student()const uni = new School()// Returns Warning: Property ‘rule’ does not exist on type ‘School’uni.roll = 5;// The following code is correct.// Comment the above code and uncomment the following// student.roll;
Конструкторы классов
Класс может иметь специальный метод или функцию, известную как «конструктор», который вызывается автоматически, когда мы создаем объект этого класса. Конструктор можно использовать для инициализации данных класса или выполнения других действий при создании экземпляров его объектов. Однако классу не обязательно включать конструктор.
Ниже пример демонстрирует класс Car с общедоступным конструктором, который автоматически вызывается при создании экземпляра объекта. В то же время конструктор создает экземпляр класса.
class Car {// define propertiesmakeAndModel: string;year: number = 0// constructor of Carpublic constructor() {this.makeAndModel = ‘Toyota Corolla’this.year = 2015}}// create an object of the classconst car = new Car()console.log(«\n\n Car make and model : » + car.makeAndModel)console.log(«\n\n Year this » + car.makeAndModel + » was manufactured: » + car.year)
Наследование классов
Другой ключевой концепцией объектно-ориентированного программирования, которую поддерживает TypeScript, является наследование. Наследование позволяет нам получить класс из другого (родительского или супер) класса, тем самым расширяя функциональность родительского класса. Вновь созданные классы называются дочерними или подклассами.
Дочерние классы наследуют все свойства и методы своего родительского класса, но не наследуют какие-либо закрытые элементы данных или конструкторы.
Вы можете использовать ключевое слово extends для получения дочерних классов от родительских классов.
Синтаксис
class child_class extends parent_class
Существует три типа наследования классов:
- Single: когда один дочерний класс наследует свойства и методы класса от одного родительского класса.
- Multiple: когда дочерний класс наследует свойства и методы класса от более чем одного родительского класса. TypeScript не поддерживает множественное наследование.
- Multi-level: когда класс наследует свойства и методы класса от другого дочернего класса (например, внука или правнука).
Модификаторы доступа
Модификатор доступа ограничивает видимость данных и методов класса.
TypeScript предоставляет три ключевых модификатора доступа:
public
private
protected
Публичные модификаторы
Все члены класса в TypeScript по умолчанию общедоступны, но в противном случае их можно сделать общедоступными с помощью publicключевого слова. Доступ к этим членам можно получить где угодно без ограничений, если модификатор не указан.
Публичный метод или данные класса могут быть доступны самому классу или любому другому классу (производному или нет).
class Fruit {public fruit_plu: number = 4129;fruit_name: string}let apple = new Fruit();apple.fruit_plu = 4129;apple.fruit_name = «Fuji Apple»;console.log («\n\n The PLU code for a «+ apple.fruit_name +» is » + apple.fruit_plu)
В приведенном выше примере ключевые слова «fruit_plu» и «fruit_name» считаются открытыми членами класса, хотя «fruit_plu» — единственное, перед которым стоит модификатор доступа. Помните, что если вы не указываете область члена класса, к нему можно получить доступ извне класса.
Частные модификаторы
Доступ privateк методу или члену данных класса невозможен из-за пределов его класса. Вы можете использовать privateключевое слово, чтобы установить его область действия. Когда область действия закрытого члена ограничена его классом, доступ к нему могут иметь только другие методы того же класса.
class Fruit {private fruit_plu: number = 4129;fruit_name: string}let apple = new Fruit();// running this console.log should return an error because it is inaccessibleconsole.log(«\n\n The PLU code for this fruit is » + apple.fruit_plu)
В приведенном выше примере попытка доступа к fruit_pluчлену вернет ошибку компилятора, поскольку его область действия была установлена на private. Вы по-прежнему сможете получить доступ к fruit_nameэлементу, поскольку его областью по умолчанию является public.
Защищенные модификаторы
Модификатор защищенного доступа работает аналогично модификатору частного доступа, за одним важным исключением. Доступ к защищенным методам или членам данных класса может получить сам класс, а также любой производный от него дочерний класс. В TypeScript вы можете использовать ключевое слово конструктора для объявления общедоступного и защищенного свойств в одном классе. Это свойства параметров, которые позволяют одновременно объявить параметр конструктора и член класса.
Примечание. Одним из предостережений системы типов TypeScript является то, что частные и защищенные области применяются только во время проверки типов во время выполнения.
Наследование с модификаторами доступа
Теперь, когда у вас есть общее представление о наследовании и модификаторах доступа, мы можем продемонстрировать, как эти две концепции можно использовать для изменения доступа к некоторым членам класса, но не к другим.
В следующем примере вы увидите, что область действия всех членов класса легкодоступна. Вы должны быть в состоянии выполнить эту программу без каких-либо ошибок.
Однако мы также включили код, который изменяет область действия различных членов класса в виде комментариев. Попробуйте раскомментировать разные участки кода, чтобы увидеть, как доступ обеспечивается различными модификаторами доступа! Вы также увидите, какие ошибки возвращает TypeScript.
//// Base classclass Car {protected makeAndModel: string;private year: number = 0// constructor of Carpublic constructor() {}//getter of make and modelpublic getMakeAndModel (): string {return this.makeAndModel;}//setter of make and modelpublic setMakeAndModel(make: string) {this.makeAndModel = make;}//getter of manufacture yearpublic getYear (): number {return this.year;}//setter of manufacture yearpublic setYear(year: number) {this.year = year;}}// Derived classclass Tesla extends Car {//public data member, for the car location.public location: string//constructor of Tesla classconstructor() {super();super.makeAndModel = ‘Tesla X’}/** Uncommenting the following will give error, because *//** year is defined private (instead of public or protected) *//*getYear (): number {return this.year;}*/}// create objects of each class.const tesla = new Tesla()const car = new Car()//setYear is public hence can be accessed from derived classtesla.setYear(2022)tesla.location = ‘New York’console.log(«\n\nMake and model of car: » + tesla.getMakeAndModel())console.log(«\n\nLocation: «+ tesla.location);// Uncommenting the following code should give error because//location is first defined in the derived class//car.location = «San Francisco»// Uncommenting the following code should give error because//year is a private variable.//tesla.year = 2001;
Интерфейсы и псевдонимы типов
Псевдонимы типов
Есть два способа определить типы для ваших данных в TypeScript: псевдонимы типов и интерфейсы.
Псевдонимы типов объявляются с использованием typeключевого слова и используются для явного аннотирования типа именем (псевдонимом). Псевдонимы типов можно использовать для представления примитивных типов данных, таких как строковые или логические, но их также можно использовать для представления objectтипов tuplesи т. д.!
В отличие от интерфейсов, псевдонимы типов не могут быть объявлены более одного раза и не могут быть изменены после создания.
Примечание : Забавный факт! Компилятор TypeScript полностью преобразует TypeScript в код JavaScript во время процесса, называемого транспиляцией. Это делается для того, чтобы все без исключения программы JavaScript были полностью совместимы с языком программирования TypeScript.
Интерфейсы
Интерфейсы — это еще один способ определить структуру данных ваших объектов. Это абстрактный тип, который сообщает компилятору TypeScript, какие свойства может иметь данный объект. В TypeScript интерфейсы предоставляют объекту синтаксис для объявления свойств, методов и событий, но определять эти члены должен производный класс. В отличие от псевдонимов типов, вы можете свободно добавлять новые поля в существующий интерфейс.
Вы можете объявить интерфейс, используя interfaceключевое слово. В этом примере мы можем определить интерфейс для яблока со свойствами «сорт» и «цвет» в виде строк.
interface Apple {variety: string;color: string}
Для реализации Appleинтерфейса мы просто присваиваем значения свойствам.
interface Apple {variety: string;color: string}let newFruit: Apple = {variety: «Opal»,color: «yellow»,};console.log(«\n\n We have a new type of apple in stock! It’s » + newFruit.color + » and of the «+ newFruit.variety + » variety.»)
Интерфейсы можно рассматривать как схему структуры данных, которой должны следовать производные классы. В приведенном ниже примере мы рассмотрим, как реализовать интерфейс с классом, используя implementsключевое слово.
interface Apple {variety: string;color: string}let newFruit: Apple = {variety: «Opal»,color: «yellow»,};console.log(«\n\n We have a new type of apple in stock! It’s » + newFruit.color + » and of the «+ newFruit.variety + » variety.»)
Интерфейсы и наследование
Как и в случае с классами, мы можем получить интерфейс из других интерфейсов посредством наследования. В отличие от классов, один интерфейс может быть расширен из нескольких интерфейсов.
В приведенном ниже примере мы демонстрируем, как интерфейс ITeacherAndStudentможет быть получен из интерфейсов IStudentand.ITeacher
// Interface for studentsinterface IStudent {roll: numberstdName: stringgetFathersName():string;}// Interface for teachersinterface ITeacher {id: numbername: string}// An interface derived from both students and teachers.interface ITeacherAndStudent extends IStudent, ITeacher {age: number}