web-dev-qa-db-ja.com

TypeScriptのクラスでダックタイピングが許可される理由

TypeScriptのように見えます(コンパイラの観点から)そのようなコードを持っていることは完全に素晴らしいです:

class Vehicle {
    public run(): void { console.log('Vehicle.run'); }
}

class Task {
    public run(): void { console.log('Task.run'); }
}

function runTask(t: Task) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle());

しかし、同時にVehicleTaskには共通点がないため、コンパイルエラーが予想されます。

また、saneの使用法は、明示的なインターフェース定義を介して実装できます。

interface Runnable {
    run(): void;
}

class Vehicle implements Runnable {
    public run(): void { console.log('Vehicle.run'); }
}

class Task implements Runnable {
    public run(): void { console.log('Task.run'); }
}

function runRunnable(r: Runnable) {
    r.run();
}

runRunnable(new Task());
runRunnable(new Vehicle());

...または一般的な親オブジェクト:

class Entity {
    abstract run(): void;
}

class Vehicle extends Entity {
    public run(): void { console.log('Vehicle.run'); }
}

class Task extends Entity {
    public run(): void { console.log('Task.run'); }
}

function runEntity(e: Entity) {
    e.run();
}

runEntity(new Task());
runEntity(new Vehicle());

そして、そうです。JavaScriptの場合、そのような振る舞いをすることはまったく問題ありません。クラスもコンパイラーもまったくなく(構文糖衣のみ)、ダックタイピングが言語にとって自然だからです。しかし、TypeScriptは静的チェック、クラス、インターフェースなどを導入しようとします。しかし、私の意見では、クラスインスタンスのダックタイピングはやや混乱し、エラーが発生しやすいように見えます。

21
Ivan Velichko

これは、構造タイピングが機能する方法です。 TypeScriptには、Javiscriptの動作を最もよくエミュレートするための構造型システムがあります。 JavaScriptはダックタイピングを使用するため、コントラクトを定義するオブジェクトはすべて、任意の関数で使用できます。 TypeScriptは、実行時ではなくコンパイル時にアヒルの入力を検証しようとします。

ただし、問題が発生するのは些細なクラスだけであり、プライベートを追加するとすぐに、同じ構造であってもクラスに互換性がなくなります。

class Vehicle {
    private x: string;
    public run(): void { console.log('Vehicle.run'); }
}

class Task {
    private x: string;
    public run(): void { console.log('Task.run'); }
}

function runTask(t: Task) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle()); // Will be a compile time error

この動作では、インターフェイスを明示的に実装しないこともできます。たとえば、関数はパラメーターのインターフェイスをインラインで定義できます。コントラクトを満たすクラスは、明示的にインターフェイスを実装していなくても互換性があります。

function runTask(t: {  run(): void }) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle());

個人的な注意として、C#から来ると、これは最初は異常なように見えましたが、拡張性に関しては、この型チェックの方法により、柔軟性が大幅に向上し、慣れれば利点がわかります。

TypeScriptを使用して名義型を作成し、コンテキストによって型を区別できるようになりました。次の質問を検討してください。

TypeScriptの原子タイプの識別(公称原子タイプ)

それは例です:

export type Kilos<T> = T & { readonly discriminator: unique symbol };
export type Pounds<T> = T & { readonly discriminator: unique symbol };

export interface MetricWeight {
    value: Kilos<number>
}

export interface ImperialWeight {
    value: Pounds<number>
}

const wm: MetricWeight = { value: 0 as Kilos<number> }
const wi: ImperialWeight = { value: 0 as Pounds<number> }

wm.value = wi.value;                  // Gives compiler error
wi.value = wi.value * 2;              // Gives compiler error
wm.value = wi.value * 2;              // Gives compiler error
const we: MetricWeight = { value: 0 } // Gives compiler error
2
Lu4