TypeScript Handbook を読む (4. Classes)

[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html) を読み進めていく第四回目。

1. [Basic Types](http://qiita.com/murank/items/9c1f633545842c953947)
1. [Variable Declarations](http://qiita.com/murank/items/913f1c1e4e725047436c)
1. [Interfaces](http://qiita.com/murank/items/553276c3dc2eec9bfe47)
1. Classes (今ココ)
1. [Functions](http://qiita.com/murank/items/e310ce68ed5a0058d42d)
1. [Generics](http://qiita.com/murank/items/144b686d34fd433a97a3)
1. [Enums](http://qiita.com/murank/items/cc487562b21bae3728ba)
1. [Type Inference](http://qiita.com/murank/items/e67ad7426020321228e9)
1. [Type Compatibility](http://qiita.com/murank/items/606efb0615294a6826e3)
1. [Advanced Types](http://qiita.com/murank/items/bc1776efbecbd2120d94)
1. [Symbols](http://qiita.com/murank/items/04b3b31caa677750f240)
1. [Iterators and Generators](http://qiita.com/murank/items/7ceec53dde886f31a682)
1. [Modules](http://qiita.com/murank/items/0fc4e059be8ea5e1c274)
1. [Namespaces](http://qiita.com/murank/items/0dfad4a0bb91aaa6c97b)
1. [Namespaces and Modules](http://qiita.com/murank/items/0be1978a3515fc76e7ba)
1. [Module Resolution](http://qiita.com/murank/items/5f11466474d49498bbd5)
1. [Declaration Merging](http://qiita.com/murank/items/ec90ba04edab1ea3718e)
1. [JSX](http://qiita.com/murank/items/90164aa9a3bed3c49a80)
1. [Decorators](https://qiita.com/murank/items/580e34883e0832ca7aed)
1. [Mixins](https://qiita.com/murank/items/e77ad8359ed1fd82822d)
1. [Triple-Slash Directives](https://qiita.com/murank/items/faff57883338ebdeda12)
1. [Type Checking JavaScript Files](https://qiita.com/murank/items/588eaddfe8af59914658)


# Classes
[原文](https://www.typescriptlang.org/docs/handbook/classes.html)

## Classes
まずは簡単な例から見ていきましょう。

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

let greeter = new Greeter("world");
```
```js:JavaScript
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());

var greeter = new Greeter("world");
```

この例では `Greater` クラスを宣言しており、メンバとして `greeting` プロパティ、コンストラクタ、`greet` メソッドを持っています。

`greet` メソッドの中で `this` を使用している点に注目してください。
このようにすることで、他のメンバにアクセスすることができます。

最後の行では `new` を使ってインスタンスを作成しています。
こうすると、事前に定義したコンストラクタが呼ばれてオブジェクトが初期化されます。


## Inheritance
TypeScript では一般的なオブジェクト指向プログラミングも可能です。
クラスベースプログラミングのもっとも基礎的なパターンのひとつが、継承を使用した既存クラスの拡張です。

例を見てみましょう。

```ts:TypeScript
class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
```
```js:JavaScript
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Animal = /** @class */ (function () {
    function Animal() {
    }
    Animal.prototype.move = function (distanceInMeters) {
        if (distanceInMeters === void 0) { distanceInMeters = 0; }
        console.log("Animal moved " + distanceInMeters + "m.");
    };
    return Animal;
}());
var Dog = /** @class */ (function (_super) {
    __extends(Dog, _super);
    function Dog() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    Dog.prototype.bark = function () {
        console.log('Woof! Woof!');
    };
    return Dog;
}(Animal));
var dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
```
>継承を実現するためにこれだけのコードが必要と考えると、altJS 万歳と言わざるをえない

この例では一番基本的な継承の機能である、基底クラスからのプロパティ、メソッドの継承を行っています。
`Dog` クラスは `extends` キーワードを使用して `Animal` *基底* クラスから *派生* しており、派生元先のクラスのことをスーパークラス、サブクラスと呼びます。

`Dog` は `Animal` の機能を拡張しているため、`Dog` クラスのインスタンスを作成して `bark()` と `move()` を呼び出すことができます。


続いて、より複雑な例を見てみましょう。

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

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);
```
```js:JavaScript
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    Animal.prototype.move = function (distanceInMeters) {
        if (distanceInMeters === void 0) { distanceInMeters = 0; }
        console.log(this.name + " moved " + distanceInMeters + "m.");
    };
    return Animal;
}());

var Snake = (function (_super) {
    __extends(Snake, _super);
    function Snake(name) {
        return _super.call(this, name) || this;
    }
    Snake.prototype.move = function (distanceInMeters) {
        if (distanceInMeters === void 0) { distanceInMeters = 5; }
        console.log("Slithering...");
        _super.prototype.move.call(this, distanceInMeters);
    };
    return Snake;
}(Animal));

var Horse = (function (_super) {
    __extends(Horse, _super);
    function Horse(name) {
        return _super.call(this, name) || this;
    }
    Horse.prototype.move = function (distanceInMeters) {
        if (distanceInMeters === void 0) { distanceInMeters = 45; }
        console.log("Galloping...");
        _super.prototype.move.call(this, distanceInMeters);
    };
    return Horse;
}(Animal));

var sam = new Snake("Sammy the Python");
var tom = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);
```

この例は上記の例では述べていない、別の機能について説明しています。
先ほどと同じく、`extends` キーワードを使用して `Animal` クラスから `Horse`、`Snake` クラスを作成しています。

上記の例との違いは、派生クラスがともにコンストラクタを持っており、基底クラスのコンストラクタを呼び出すために `super()` を **呼び出さなければならない** ことです。
もっと言うと、コンストラクタ内でプロパティにアクセスする前には *常に* `super()` を呼び出す必要があります。
これは重要なルールであり、TypeScript ではこれを強制しています。
>`super()` メソッドの呼び出しを忘れてもコンパイルエラーになるので大丈夫

さらに、この例では親クラスのメソッドをサブクラスでオーバーライドする方法についても示しています。
この時、仮に `tom` が `Animal` クラスとして宣言されていたとしても、`tom.move(34)` を実行すると `Horse` クラスの `move()` が呼び出されます。
>Java とかを使ってるとこれはお馴染みね

```
Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
```

## Public, private, and protected modifiers
### Public by default
TypeScript はデフォルトですべてのメンバが `public` です。
もちろん、明示的に `public` キーワードを付与しても問題ありません。

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

### Understanding `private`
メンバに `private` キーワードを付与すると、そのクラス外からアクセスできなくなります。

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

new Animal("Cat").name; // エラー。'name' は private なのでアクセスできない
```
```js:JavaScript
var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());

new Animal("Cat").name; // エラー。'name' は private なのでアクセスできない
```
>`private` メンバとして宣言しても JavaScript のコードが変わるわけでもなく、コンパイラでのチェックが有効になるだけなのね

TypeScript では 2 つの型が等しいか判断する時に、それらがどこで宣言されたかによらず、すべてのメンバに互換性があるかどうかだけを基に判断します。
しかし、クラスが `private` または `protected` メンバを持つ場合、それらのメンバが同じクラスで宣言されたものの場合のみ、型が等しいとみなします。

```ts:TypeScript
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; // OK。'Animal' と 'Rhino' の 'name' プロパティは両方とも 'Animal' クラスで宣言されている
animal = employee; // エラー。'Employee' の 'name' プロパティは 'Animal' クラスで宣言されたものではない
```

### Understanding `protected`
`protected` は `private` と似ていますが、宣言したクラスとそのサブクラスからのみアクセスできる点が異なります。

```ts:TypeScript
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() {
        // ここからは 'name' にアクセスできる
        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' にアクセスできない
```

コンストラクタを `protected` として宣言することもできます。
その場合、インスタンス化することができず、継承のみが可能なクラスとなります。

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

// 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"); // エラー。'Person'  クラスのコンストラクタは protected
```

## Readonly modifier
`readonly` キーワードをつけることでプロパティを読み取り専用にすることが可能です。

```ts:TypeScript
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' は読み取り専用
```

### Parameter properties
コンストラクタで受け取った引数を基にプロパティを初期化するだけであれば、*パラメータプロパティ* を使用することで一度に宣言と初期化を行うことができます。
パラメータプロパティを宣言するには、コンストラクタの引数にアクセス修飾子 (`private`、`protected`、`public`) や `readonly` を付与します。

```ts:TypeScript
class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) {
    }
}
```
```js:JavaScript
var Octopus = (function () {
    function Octopus(name) {
        this.name = name;
        this.numberOfLegs = 8;
    }
    return Octopus;
}());
```
>便利は便利だけど、ぱっと見でプロパティの一覧が分かりにくくなるのはデメリットかなぁ

## Accessors
TypeScript では getter/setter を宣言することで、プロパティへのアクセスを捕捉することができる他、より細かくプロパティへのアクセスを制御することが可能です。

以下の getter/setter を使用していない例では、`fullName` を好きなように変更することができてしまいます。

```ts:TypeScript
class Employee {
    fullName: string;
}

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

以下の例では setter を宣言して `fullName` の設定時に追加のチェックを行うようにしています。
また、通常通り `fullName` にアクセスできるよう、getter も宣言しています。

```ts:TypeScript
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);
}
```
```js:JavaScript
var passcode = "secret passcode";

var Employee = (function () {
    function Employee() {
    }
    Object.defineProperty(Employee.prototype, "fullName", {
        get: function () {
            return this._fullName;
        },
        set: function (newName) {
            if (passcode && passcode == "secret passcode") {
                this._fullName = newName;
            }
            else {
                console.log("Error: Unauthorized update of employee!");
            }
        },
        enumerable: true,
        configurable: true
    });
    return Employee;
}());

var employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}
```
>JavaScript ではこうやって getter/setter を実現するのかー & TypeScript 使うと宣言が楽すぎる…

アクセッサを宣言する場合、ECMAScript 5 以上のコードを生成するようにコンパイラを設定する必要があります。
また、`get` のみを宣言して `set` を宣言しなかった場合、自動的に `readonly` なプロパティとして扱われます。


## Static Properties
`static` キーワードを使用することで *静的* メンバを宣言することが可能です。
インスタンスメンバにアクセスする場合に `this` を前に付ける必要があったように、静的メンバにアクセスする場合にはクラス名を前に付ける必要がある点に注意してください。

```ts:TypeScript
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}));
```
```js:JavaScript
var Grid = (function () {
    function Grid(scale) {
        this.scale = scale;
    }
    Grid.prototype.calculateDistanceFromOrigin = function (point) {
        var xDist = (point.x - Grid.origin.x);
        var yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    };
    return Grid;
}());
Grid.origin = { x: 0, y: 0 };

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

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


## Abstract Classes
`abstract` キーワードを使用することで、抽象メソッドを持つ抽象クラスを宣言することができます。
抽象メソッドはインタフェースのメソッド宣言と似ていますが、`abstract` キーワードを付与する必要がある点が異なります。


```ts:TypeScript
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(); // エラー。抽象クラスをインスタンス化することはできない
department = new AccountingDepartment(); // 抽象クラスでないサブクラスをインスタンス化し、代入することは可能
department.printName();
department.printMeeting();
department.generateReports(); // エラー。抽象クラスで宣言されていないメンバにはアクセスできない
```
```js:JavaScript
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

var Department = (function () {
    function Department(name) {
        this.name = name;
    }
    Department.prototype.printName = function () {
        console.log("Department name: " + this.name);
    };
    return Department;
}());

var AccountingDepartment = (function (_super) {
    __extends(AccountingDepartment, _super);
    function AccountingDepartment() {
        return _super.call(this, "Accounting and Auditing") || this;
    }
    AccountingDepartment.prototype.printMeeting = function () {
        console.log("The Accounting Department meets each Monday at 10am.");
    };
    AccountingDepartment.prototype.generateReports = function () {
        console.log("Generating accounting reports...");
    };
    return AccountingDepartment;
}(Department));

var department; // 抽象クラスの変数を宣言することは可能
department = new Department(); // エラー。抽象クラスをインスタンス化することはできない
department = new AccountingDepartment(); // 抽象クラスでないサブクラスをインスタンス化し、代入することは可能
department.printName();
department.printMeeting();
department.generateReports(); // エラー。抽象クラスで宣言されていないメンバにはアクセスできない
```


## Advanced Techniques
### Constructor functions
TypeScrpit でクラスを宣言するにあたって、複数の方法で宣言することができます。

1 つ目の方法は以下の通りで、`greeter` の宣言の時に `Greeter` を型名として使用しています。
これは大半のオブジェクト指向プログラマにとって馴染みのある方法でしょう。

```ts: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());
```

2 つ目の方法は *コンストラクタ関数* と呼ばれる方法で、以下の例では `let Greeter` にコンストラクタ関数を代入して使用しています。
この時、コンストラクタにはすべての静的メンバが含まれています。

```ts:TypeScript
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());
```

これについてもう少し詳しく説明するために、例を少し修正してみましょう。

```ts:TypeScript
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()); // "Hello, there"

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

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet()); // "Hey there!"
```

`greeter1` は最初の例と同じように動作します。
次に、`typeof Greeter` を使用して `greeterMaker` 変数に `Greeter` クラスそのもの (コンストラクタ関数) を代入しています。
これには `Greeter` のすべての静的メンバと、インスタンスを作成するためのコンストラクタ関数が含まれています。
>個人的には 1 番目の宣言の方が自明なので、なるべくこちらを使う方が良い気がしてるけど、2 番目の宣言方法はどういう時に使うべきなんだろう？

### Using a class as an interface
クラスをインタフェースとして使用することもできます。

```ts:TypeScript
class Point {
    x: number;
    y: number;
}

interface Point3d extends Point { // クラスを使ってインタフェースを拡張している!
    z: number;
}

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