В этой части продолжаем погружаться в TypeScript
. В предыдущей части был рассмотрен исчерпывающий набор конструкций, который позволяет писать код в структурном стиле. В структурном стиле можно реализовать любой вычислимый алгоритм, но запись может оказаться громоздкой. Поэтому в данной части рассмотрим набор конструкций, которые позволят эту запись улучшить.
Для работы с руководством можно использовать песочницу: https://www.typescriptlang.org/play
Рассмотрим уже знакомый пример функции нахождения периметра прямоугольника:
function calculatePerimeter(w: number, h: number) : number | string { // сигнатура функции
if (w > 0 && h > 0) {
return 2 * (w + h);
} else {
return "Некорректная ширина или высота!"
}
}
let width = 7;
let height = 14;
let perimeter = calculatePerimeter(width, height);
console.log(perimeter);
console.log("Программа завершила работу");
В сигнатуре функции можно заметить, что наряду с вычисляемым значением функция может вернуть строку с ошибкой. Нам пришлось пойти на такой шаг, чтобы была возможность просигнализировать о некорректных параметрах.
Существует более корректный способ уведомления вызывающего кода о ситуациях, которые могут возникнуть при выполнении программы и приводят к невозможности (бессмысленности) дальнейшей отработки программой её базового алгоритма -- исключения.
function calculatePerimeter(w: number, h: number) : number { // сигнатура функции
if (w > 0 && h > 0) {
return 2 * (w + h);
} else {
throw new Error("Некорректная ширина или высота!") // выброс исключения
}
}
let width = -7;
let height = 14;
let perimeter = calculatePerimeter(width, height);
console.log(perimeter);
console.log("Программа завершила работу");
Можно увидеть что в программе изменилось две строки: строка с сигнатурой функции и строка с уведомлением о некорректности аргументов.
Теперь нам не требуется указывать string
в сигнатуре функции, т.к. ни при каких условиях функция не вернет строку.
Вместо возврата строки используется выброс исключения:
throw
-- ключевое слово, которое используется для сигнализации о некорректной ситуацииnew Error("Текст ошибки")
-- создание сущности с информацией об ошибке.new
и Error
станет понятно далее, сейчас достаточно того, что эти конструкции помогают сигнализировать об исключительных ситуациях.При запуске примера можно увидеть, что сообщение об ошибке выводится, но сообщение о завершении программы не вывелось. Такая ситуация возникла потому, что нигде не нашлось кода, который мог бы обработать исключение. Если исключения не обрабатывать, то они приводят к завершению работы программы.
Чтобы обработать исключение нам понадобится следующая конструкция:
try {
// блок, где осуществляется выполнение кода, который может привести к исключению
} catch(e) {
// блок, где осуществляется обработка исключения, если оно было выброшено
}
Давайте добавим блок try-catch
в программу, чтобы она не завершалась досрочно:
function calculatePerimeter(w: number, h: number) : number { // сигнатура функции
if (w > 0 && h > 0) {
return 2 * (w + h);
} else {
throw new Error("Некорректная ширина или высота!") // выброс исключения
}
}
let width = -7;
let height = 14;
try{ // попытка выполнить код, который может привести к исключению
let perimeter = calculatePerimeter(width, height);
console.log(perimeter);
} catch(e){ // блок обработки возникшего исключения
console.log(e);
}
console.log("Программа завершила работу");
Вспомним как делали массив массивов чисел (Array<Array<number>>
), чтобы хранить в одной переменной сразу несколько прямоугольников:
function calculatePerimeter(w: number, h: number) : number {
if (w > 0 && h > 0) {
return 2 * (w + h);
} else {
throw new Error("Некорректная ширина или высота!")
}
}
let rectangles: Array<Array<number>> = [[7, 14], [-7, 14], [7, -14]];
for (let i = 0; i < rectangles.length; i++){
let currentRectanle = rectangles[i];
try{
let perimeter = calculatePerimeter(currentRectanle[0], currentRectanle[1]);
console.log(perimeter);
} catch(e){
console.log(e);
}
}
Проблема этого примера в том, что описание одного прямоугольника хранится в виде Array<number>
. Вот некоторые из проблем, которые нас подталкивают к мысли, что использование Array<number>
для прямоугольника неуместно:
[7, 14]
-- какое из числе ширина, а какое высота?[7, 14, 7]
-- можем увеличить количество чисел в записи.[7]
-- можем уменьшить количество чисел в записи.Для решения всех перечисленных проблем давайте воспользуемся объектами:
function calculatePerimeter(w: number, h: number) : number {
if (w > 0 && h > 0) {
return 2 * (w + h);
} else {
throw new Error("Некорректная ширина или высота!")
}
}
type Rectangle = { // описание объекта в виде типа
width: number;
height: number;
}
let rectangles: Array<Rectangle> = [{width: 7, height: 14}, {width: -7, height: 14}, {width: 7, height: -14}];
for (let i = 0; i < rectangles.length; i++){
let currentRectanle = rectangles[i];
try{
let perimeter = calculatePerimeter(currentRectanle.width, currentRectanle.height); // можно обращаться по имени свойств
console.log(perimeter);
} catch(e){
console.log(e);
}
}
Задание 1:
Дан список описаний людей: рост и вес. Написать функцию, которая будет считать индекс массы тела: .
Посчитать ИМТ для всех людей.
Класс -- модель для создания новых объектов. По сути это способ описания новых типов. С помощью классов можно описывать данные (набор полей и их состояния) и операции (набор методов), которые свойственны объектам данного типа.
Рассмотрим попытку описсать с помощью класса положительные числа:
class PositiveNumber{
value: number;
constructor(value: number) {
if (value > 0){
this.value = value;
} else {
throw new Error("Значение должно быть больше нуля!");
}
}
}
let a: PositiveNumber = new PositiveNumber(10);
console.log(a.value);
Но мы можем поломать и присвоить некорректное значение:
class PositiveNumber{
value: number;
constructor(value: number) {
if (value > 0){
this.value = value;
} else {
throw new Error("Значение должно быть больше нуля!");
}
}
}
let a: PositiveNumber = new PositiveNumber(10);
a.value = -10;
console.log(a.value);
Давайте сделаем поле приватным (ограничим доступ за пределами класса), а доступ к свойству value
организуем через геттер (свойство-аксессор)
.
Свойства-аксессоры (геттеры, сеттеры) -- по своей сути это функции, которые используются для присвоения и получения значения, но во внешнем коде они выглядят как обычные свойства объекта.
class PositiveNumber{
private _value: number;
constructor(value: number) {
if (value > 0){
this._value = value;
} else {
throw new Error("Значение должно быть больше нуля!");
}
}
public get value(): number {
return this._value;
}
}
let a: PositiveNumber = new PositiveNumber(10);
console.log(a.value);
Чтобы появилась возможно менять храниемое значение, нужно добавить геттер:
class PositiveNumber{
private _value: number;
constructor(value: number) {
this._value = this.checkValue(value);
}
public get value(): number {
return this._value;
}
public set value(val: number) {
this._value = this.checkValue(val);
}
private checkValue(val: number): number {
if (val > 0){
return val;
} else {
throw new Error("Значение должно быть больше нуля!");
}
}
}
let a: PositiveNumber = new PositiveNumber(10);
a.value = -10;
console.log(a.value);
Задание 2:
исправить код с подсчётом периметра. Добавить код с расчётом площади.
Интерфейс определяет свойства и методы, которые объект должен реализовать. Другими словами в интерфейсе достаточно только указать что должно быть в объекте, а реализация этих методов и свойств будет возложена полностью на класс или объект, который этот интерфейс реализовывают. Подробнее про интерфейсы
class PositiveNumber{
private _value: number;
constructor(value: number) {
this._value = this.checkValue(value);
}
public get value(): number {
return this._value;
}
public set value(val: number) {
this._value = this.checkValue(val);
}
private checkValue(val: number): number {
if (val > 0){
return val;
} else {
throw new Error("Значение должно быть больше нуля!");
}
}
}
// Можно указывать методы
interface IAreaMethod {
calcArea(): PositiveNumber;
}
class Square implements IAreaMethod{
calcArea(): PositiveNumber {
return new PositiveNumber(42);
}
}
let f1: IAreaMethod = new Square();
console.log(f1.calcArea().value);
// Можно указывать свойства
interface IAreaProperty {
readonly area: PositiveNumber;
}
class Rectangle implements IAreaProperty{
public get area(): PositiveNumber {
return new PositiveNumber(42);
}
}
class Circle implements IAreaProperty{
public get area(): PositiveNumber {
return new PositiveNumber(24);
}
}
let f2: IAreaProperty = new Rectangle();
console.log(f2.area.value);
let f3: IAreaProperty = new Circle();
console.log(f3.area.value);
// Можно сложить объекты разных классов в один массив:
var figures: Array<IAreaProperty> = [f2, f3];
console.log(figures);
Задание 3:
1.Создать интерфейсIPrintableFigure
. В этом интерфейсе должны быть свойства для чтения описания(description), площади(area) и периметра(perimeter) фигуры. Описание должно предоставлять название фигуры и её свойства (длина стороны, радиус и т.д., при наличии).
2.Создать классы квадрат(Square), прямоугольник(Rectangle) и круг(Circle), которые реализуют интерфейсIPrintableFigure
.
3.Создать методFigurePrinter
, который принимает массив из IPrintableFigure и выводит информацию (описание, площадь, периметр) по каждой.