実装していて 「構造的部分型」 なんて意識していなかったけど、「構造的部分型」をちょっと理解していると、下記のサンプルがなんで実装エラー(静的型チェックエラー)にならず、実行できるのか理解できるよ、っていう話。
class Person {
public talk() {
console.log('こんちは');
}
}
class Dog {
public talk() {
console.log('ワン!');
}
}
const person = new Person();
const dog: Dog = person; // ←これエラーにならないよ!!
dog.talk();
実行結果
こんにちは
PersonクラスってDogクラスから派生してるわけでもなく、なんでエラーにならないんだ??
Javaなら間違いなくエラーになりますよね。
このエラーにならない理由が、TypeScriptが「構造的部分型(構造的型付け)」型システムを採用しているからです。
構造的部分型は、型の「名前」や「継承関係」ではなく、オブジェクトが持っている「プロパティ」が互換しているかどうか、つまり構造が一致しているかどうかで判断します。
先ほどの例では、PersonもDocもtalkメソッドを持っているので、構造的に一致している為エラーになりませんでした。
なので、こちらはエラーになります。
class Person {
public talk() {
console.log('こんちは');
}
}
class Dog {
public bark() {
console.log('ワン!');
}
}
const person = new Person();
// エラー: Property 'bark' is missing in type 'Person' but required in type 'Dog'.ts(2741)
const dog: Dog = person;
構造的部分型について詳細を理解したい方は、こちらをどうぞ。
ところで、Javaは「公称型(名前的型付け)」型システムを採用し、型の「名前」、「継承関係」で型が互換しているかどうか判断します。
公称型については、こちらをどうぞ。
TypeScriptもクラスを公称型クラスにすることは可能です。
公称型クラスにすることによって、構造が同じでも型不一致になります。
公称型クラスにするには、非publicなプロパティが1つでもあると公称型クラスになります。
以下のサンプルは、公称型クラスなので、PersonクラスのインスタンスをDogクラスの変数に代入すると実装エラーになります。
class Person {
private _name: string;
public constructor(name: string) {
this._name = name;
}
public talk() {
console.log('こんちは');
}
}
class Dog {
private _name: string;
public constructor(name: string) {
this._name = name;
}
public talk() {
console.log('ワン!');
}
}
const person = new Person('あおき');
// エラー: Type 'Person' is not assignable to type 'Dog'. Types have separate declarations of a private property '_name'.ts(2322)
const dog: Dog = person;
interfaceで公称型にすることができるそうなのですが、TypeScriptの仕様ではなく、テクニックで実現できるようです。
興味がある方は、こちらをどうぞ。