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

publicпо умолчанию

В TypeScript же, каждый член класса будетpublicпо умолчанию.

Но мы можем пометить члены классаpublicявно. Пример:

class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

private

Когда член класса помечен модификатором private, он не может быть доступен вне этого класса. Например:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // ошибка: 'name' is private;

TypeScript — это структурная система типов. Когда мы сравниваем два разных типа, независимо от того где и как они описаны и реализованы, если типы всех их членов совместимы, можно утверждать, что и сами типы совместимы. Впрочем, когда сравниваются типы с модификатором доступа private, это происходит по-другому. Два типа будут считаться совместимыми, если оба члена имеют модификатор private из того же самого объявления. Это относится и к protected членам.

Давайте посмотрим пример, чтобы понять принцип работы на практике:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
    constructor() { super("Rhino"); }
}

class Employee {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee; // ошибка: 'Animal' and 'Employee' are not compatible

В этом примере у нас есть классы Animal и Rhino, где Rhino является подклассом Animal. У нас также есть новый класс Employee, который выглядит идентично Animal. Мы создаем экземпляры этих классов и пытаемся получить доступ к каждому, чтобы посмотреть что произойдет. Поскольку private часть Animal и Rhino объявлена в одном и том же объявлении, они совместимы. Тем не менее, это не относится к Employee. Когда мы пытаемся присвоить Employee к Animal, мы получаем ошибку: эти типы не совместимы. Несмотря на то, что Employee имеет private член под именем name, это не тот член, который мы объявили в Animal.

protected

Модификатор protected действует аналогично private за исключением того, что члены, объявленные protected, могут быть доступны в подклассах. Например:

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // ошибка

Обратите внимание на то, что мы не можем использовать членnameвне классаPerson, но можем использовать внутри метода подклассаEmployee, потому чтоEmployeeпроисходит отPerson.

Конструктор тоже может иметь модификаторprotected. Это означает, что класс не может быть создан за пределами содержащего его класса, но может быть наследован. Например:

class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}

// Employee can extend Person
class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // ошибка: The 'Person' constructor is protected

Модификаторreadonly

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

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // ошибка! name is readonly.

Аксессоры (геттеры/сеттеры)

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

Давайте перепишем простой класс с использованиемgetиset. Для начала запишем пример без использования геттеров и сеттеров.

class Employee {
    fullName: string;
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}

Разрешать напрямую устанавливатьfullName- довольно удобно, но это может привести к проблемам если кто-то захочет изменить имя по своему желанию.

В этой версии мы проверяем наличие у пользователя секретного пароля, перед тем как позволить ему внести изменения. Мы делаем это заменяя прямой доступ кfullNameи используем сеттерset, который проверяет пароль. Кроме того, добавляем соответствующийget, чтобы код работал так же, как и в предыдущем примере.

let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}

Чтобы убедиться, что наш метод доступа проверяет пароль, мы можем модифицировать его и увидеть, что при несовпадении мы получаем сообщение о том, что не можем модифицировать объект работника.

Внимание: аксессоры требуют установки в компиляторе генерации кода по стандарту ECMAScript 5 или выше.

Статические свойства

До сих пор мы говорили только об членах экземпляра класса, тех, которые появляются в объекте, когда он инициализирован. Но мы можем создавать и статические члены класса, те, которые видны в классе без создания экземпляра. В этом примере мы используем static, так как origin— это общее значение для всех объектов. Каждый экземпляр получает доступ к этому значению, предваряя его именем класса. Схоже с тем, как мы добавляем this. для доступа к членам экземпляра, для доступа к статическим членам используется Grid..

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

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

Абстрактные классы — это базовые классы, от которых наследуются другие. Их экземпляры не могут быть созданы напрямую. В отличие от интерфейса, абстрактный класс может содержать детали реализации своих членов. Ключевое словоabstractиспользуется для определения абстрактных классов, а также абстрактных методов в рамках таких классов.

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log("roaming the earth...");
    }
}

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

abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log("Department name: " + this.name);
    }

    abstract printMeeting(): void; //  должен быть реализован в производном классе
}

class AccountingDepartment extends Department {

    constructor() {
        super("Accounting and Auditing"); // конструкторы в производных классах должны вызывать super()
    }

    printMeeting(): void {
        console.log("The Accounting Department meets each Monday at 10am.");
    }

    generateReports(): void {
        console.log("Generating accounting reports...");
    }
}

let department: Department; // окей, создана ссылка на абстрактный класс
department = new Department(); // ошибка: cannot create an instance of an abstract class
department = new AccountingDepartment(); // окей, создан и присвоен не абстрактный класс
department.printName();
department.printMeeting();
department.generateReports(); // ошибка: method doesn't exist on declared abstract type

Дополнительные методы

Когда вы объявляете класс в TypeScript, вы фактически создаете несколько объявлений одновременно. Первое объявление — тип экземпляра класса.

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

В данном случае, когда мы говоримlet greeter: Greeter, мы используемGreeterкак тип экземпляров классаGreeter. Это почти привычка программистов из других объектно-ориентированных языков программирования.

Мы также создаем еще одно значение, которое называетсяфункцией-конструктором. Эта функция вызывается, когда мы создаем экземпляры класса с помощьюnew. Чтобы посмотреть, как это выглядит на практике, давайте посмотрим на код JavaScript, сгенерированный компилятором из примера выше:

let Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

Здесь let Greeterприсваивается функция-конструктор. Когда мы указываем newи запускаем эту функцию, мы получаем экземпляр класса. Функция-конструктор также содержит все статические члены класса. Другой способ думать о каждом классе: есть часть экземпляр и статическая часть.

Давайте изменим немного код, чтобы показать эту разницу:

class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

В этом примереgreeter1работает аналогично тому, что выше. Мы создали экземпляр классаGreeterи используем объект. Это мы уже видели.

Дальше используем непосредственно класс. Создаем новую переменную с именемgreeterMaker. Эта переменная будет содержать сам класс, или, другими словами, функцию-конструктор. Здесь мы используемtypeof Greeter, это выглядит как "дайте мне тип самого классаGreeter", а не экземпляра. Или, точнее, "дайте мне тип идентификатора, что зоветсяGreeter", который является типом функции-конструктора. Этот тип будет содержать все статические членыGreeter, вместе с конструктором, который создает экземпляры классаGreeter. Мы продемонстрировали это, использовавnewсgreeterMaker, создавая новые экземплярыGreeterи вызывая их, как раньше.

Использование класса в качестве интерфейса

Как мы уже говорили в предыдущем разделе, объявление класса создает две вещи: тип, описывающий экземпляры класса, и функцию-конструктор. Так как классы создают типы, мы можем использовать так же, как интерфейсы.

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

results matching ""

    No results matching ""