Classの使い方(継承, アクセス修飾子, 抽象クラス)

TypescriptにおけるClassの活用方法を確認します。ES2015のClass機能はprivateメンバを定義できないなど機能の不十分さを感じます。TypescriptのClass機能は、ES2015のClass機能より充実しています。

クラスの導入
( property, constructor, method )

まずは、簡単なクラスで動作確認します。property constructor method を定義してます。

class Animal {
    // property
    name: string;
    old: number;

    // constructor
    constructor(name: string, old: number) {
        this.name = name;
        this.old = old;
    }

    // method
    showName() {
        console.log(this.name)
    }
    showOld() {
        console.log(this.old)
    }
}

export default Animal
import Animal from './animal'

const animal = new Animal('ジョン', 22);
animal.showName(); // ジョン
animal.showOld(); // 22

継承
( extends, super )

継承の動作確認をします。

class Animal {
    name: string;
    old: number;

    constructor(name: string, old: number) {
        this.name = name;
        this.old = old;
    }

    showName() {
        console.log(this.name)
    }

    showOld() {
        console.log(this.old)
    }
}

class Dog extends Animal {
    weight: number;

    constructor(name: string, old: number, weight: number) {
        super(name, old);
        this.weight = weight;
    }

    showWeight() {
        console.log(this.weight)
    }
}

const dog = new Dog('ジョン', 3, 20);
dog.showName();    // ジョン
dog.showOld();     // 3
dog.showWeight();  // 20

extends で継承します。super で継承元のconstructorを呼び出しています。

アクセス修飾子
( public, private, protected, readonly )

下記アクセス修飾子のパターンを動作確認します。

  • public
    • デフォルトなので、明示しない場合も public になります。
  • private
    • 自インスタンスのみアクセスできます。
  • protected
    • 継承先classのインスタンスでもアクセスできます。
  • readonly
    • public readonly となります。
  • private readonly

readonly は、宣言時、またはコンストラクタ内で初期化する必要があります。Parameter properties という方法で設定することもできます。

class Base {
    public a: number;
    private b: number;
    protected c: number;
    private readonly d: number = 40;
    constructor(
        a: number,
        b: number,
        c: number,
        readonly e: number  // Parameter properties
    ) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    showA() {
        console.log(this.a);
        this.a = 1;
    }

    showB() {
        console.log(this.b);
        this.b = 1;
    }

    showC() {
        console.log(this.c);
        this.c = 1;
    }

    showD() {
        console.log(this.d);

        // readonlyのためError
        // this.d = 1;
    }

    showE() {
        console.log(this.e);

        // readonlyのためError
        // this.e = 1;
    }
}

class Child extends Base {
    showA() {
        console.log(this.a);
        this.a = 1;
    }

    showB() {
        // privateのためError(Private member is not accessible)
        // console.log(this.b);
        // this.b = 1;
    }

    showC() {
        console.log(this.c);
        this.c = 1;
    }

    showD() {
        // private readonlyのためError
        // console.log(this.d);
        // this.d = 1;
    }

    showE() {
        console.log(this.e);

        // readonlyのためError
        // this.e = 1;
    }
}

const child = new Child(10, 20, 30, 50);
console.log(child.a);  // 10

// privateのためError
// console.log(child.b);

// protectedのためError
// console.log(child.c);

// private readonlyのためError
// console.log(child.d);

console.log(child.e);  // 50

Accessors
( set, get )

class Animal {
    private _name: string;

    get name(): string {
        return this._name;
    }

    set name(newName: string) {
        console.log(`Set name: ${newName}`);
        this._name = newName;
    }
}

const animal = new Animal();
console.log(animal.name);
animal.name = 'abcdef';
console.log(animal.name);

Accessorsは、targetが ECMAScript 5 以上でないと利用できません。

$ tsc -t ES5  accessors.ts 
$ node accessors.js 
undefined
Set name: abcdef
abcdef

Static
( 静的メンバ )

class StaticTest {
    static count = 0;

    constructor(private name: string) {
        StaticTest.count++;
    }

    public instanceMethod() {
        return `name: ${this.name} count: ${StaticTest.count}`;
    }

    static staticMethod() {
        return StaticTest.count;
    }
}

const staticTest1 = new StaticTest('xxxxx');
console.log(staticTest1.instanceMethod());  // name: xxxxx count: 1
console.log(StaticTest.count);              // 1
console.log(StaticTest.staticMethod());     // 1

const staticTest2 = new StaticTest('yyyyy');
console.log(staticTest2.instanceMethod());  // name: yyyyy count: 2
console.log(StaticTest.count);              // 2
console.log(StaticTest.staticMethod());     // 2

静的メンバには、クラス名.静的メンバ名 でアクセスします。

Abstract
( 抽象クラス )

abstract class AbstractClass {
    protected constructor(private name: string, protected old: number) {
    }

    showInfo(): void {
        console.log(`name: ${this.name}, ${this.showDetail()}`);
    }

    protected abstract showDetail(): string;
}

class ExtendedClass extends AbstractClass {
    constructor(name: string, old: number, private weight: number) {
        super(name, old);
    }

    showDetail(): string {
        return `old: ${this.old}, weight: ${this.weight}`;
    }
}

const extendedClass = new ExtendedClass('ジョン', 3, 20);
extendedClass.showInfo(); // name: ジョン, old: 3, weight: 20
  • 抽象クラスは抽象メソッドを持ちます。
    • 抽象クラスに abstract をつけます。
    • 抽象メソッドに abstract をつけます。
  • 抽象クラスを継承したクラスは、抽象メソッドを実装する必要があります。

参考