TypeScriptのクラスとインターフェイス!定義・取得・初期化・継承などの方法。

TypeScriptのクラスとインターフェイス!定義・取得・初期化・継承などの方法。

この記事では、TypeScriptのクラスとインターフェイスについて実装例と使い方を紹介したいと思う。

なお、著者はWebアプリの開発に携わるまではJavaプログラマとして数年間実務経験を積んでいる。

この記事ではJavaやC++といったオブジェクト指向のプログミング言語をバックボーンに持つ人にも理解しやすいように書いていく。

なお、クラスを説明するうえで「オブジェクト指向」は切り離せないコンセプトだが、ここではオブジェクト指向については特に説明しない。

TypeScriptのクラス

まずはTypeScriptのクラスについて説明していこう。

JavaScriptのクラスとの違い

本題に入る前にJavaScriptのクラスとTypeScriptのクラスの違いについて説明しておきたい。

JavaScriptは長い間クラスのないプログラミング言語だったが、ECMASCript2015よりクラスは導入された。

ただしJavaScriptのクラスとTypeScriptのクラスの間には若干の違いがある。

両者の違いはおもに以下の3点だ。

  • TypeScriptのクラスのフィールドにはアクセス修飾子がある。
  • TypeScriptのクラスにはinterfaceを実装することができる。
  • TypeScriptのクラスはジェネリックスをサポートしている。

ただし、これらの機能はJavaScriptの今後のアップデートでサポートされていく可能性はある。

基本的なクラスの記述

まずは基本的なクラスの記述方法から確認しよう。

TypeScriptのクラスは細かい文法に違いはあるものの、JavaやC++などオブジェクト指向言語のものとあまり変わらない。

export class Human {

  public name: string;
  public age: number;
  public telephone: string;

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

  public makeAppointment(date: Date) {
    console.log('Make appointment at ' + date.toDateString());
  }
}

class修飾子の前にexport修飾子を付けた場合、そのクラスはプロジェクト全体からアクセスできる。

クラスはフィールドとメソッドを持ちそれぞれにはアクセス修飾子を持つ。

コンストラクターを備えており、値の初期化などの処理を記述することができる。

作ったクラスは以下のようにインスタンスを生成することができる。

new Human('Sugita Rie', 25, '012-345-6789');

クラスの継承

次にクラスの継承について確認しよう。

この例では、extends修飾子を使い先の章で作成したHumanクラスを継承している。

親クラスのコンストラクタを呼ぶためにsuper()を使用している。

export class Teacher extends Human {

  public subjects: Array<string>;

  constructor(name: string, age: number, telephone: string, subjects: Array<string>) {
    super(name, age, telephone);
    this.subjects = subjects;
  }

  public makeAppointment(date: Date) {
    console.log('Make appointment with a teacher ' + this.name + ' at ' + date.toDateString());
  }
}

JavaやC++同様にメソッドをオーバーライドすることができる。

継承したクラスも同様にインスタンスを生成することができる。

 new Teacher('Nomura Tamotsu', 45, '012-345-5123', ['English']);

抽象クラス(アブストラクトクラス)

抽象クラスは実装を持たないクラスで同じフィールドやメソッドを持つクラス群のひな型となる。

抽象クラスの記述方法もその他のオブジェクト指向言語と大きな違いはない。

export abstract class Transportation {
  public abstract price: number;
  public abstract buyTicket(date: Date, amount: number): void;
}

export class Train implements Transportation {
  public price = 3200;

  buyTicket(date: Date, amount: number): void {
    console.log('buy ' + amount + '  of train tickets for the date ' + date);
  }
}

export class Bus implements Transportation {
  public price = 1600;

  buyTicket(date: Date, amount: number): void {
    console.log('buy ' + amount + '  of bus tickets for the date ' + date);
  }
}

TypeScriptのインターフェイス

次にTypeScriptのインターフェイスについて見ていこう。

TypeScriptのプロジェクトでは、モデルの定義の際にクラスよりもインターフェイスが使用されることのほうが多い。

インターフェイスを定義する

インターフェイスの定義の例を見ていこう。

以下は商品情報を納めたArticleオブジェクトの例だ。

export interface Article {
  id: number;
  name: string;
  brand?: string;
  price: number;

  buy(cache: string): void;
}

interfaceを定義する際には、プロパティ名とデータ型をセミコロンで区切って書く。

インターフェイスでは、オブジェクトのプロパティとメソッドを定義することができる。

名前の後に「?」がつけられているプロパティは任意入力となる。

こうしてinterfaceとして定義されたオブジェクトはコード内では以下のように使用する。

const computer = <Article> {
id: 1,
name: 'Hi spec PC',
brand: 'dell',
price: 49800
}

必須のプロパティを定義していない場合はコンパイルエラーが起きる。

インターフェイスの継承

クラスと同様にinterfaceも継承することができる。

export interface OnlineArticle extends Article {
  shipping: string;
}

インターフェイスの併合

同じ名前のinterfaceが同じスコープに複数存在する場合、それらのインターフェイスはマージされ、それぞれのプロパティを併せ持つこととなる。これは「Declaration Merging」と呼ばれている。

export interface Article {
  id: number;
  name: string;
}

export interface Article {  
  color: string;
}
const computer = <Article> {
  id: 3,
  name: 'Keyboard',
  color: 'black',
}

なお、この際に同じ名前のプロパティが異なるデータ型を持つ場合はエラーとなる。