web-dev-qa-db-ja.com

Typescript:クラスをインターフェースとして使用

クラスを別のクラスのインターフェイスとして使用しようとしました。これで、テスト用の最新のmockClassを達成しようとしています。

https://angular.io/guide/styleguide#interfaces に記載されているように、これは可能であり、好ましいアプローチです。

そして、ここに示します: Angular2のインターフェイスとしてクラスをエクスポート

しかし、奇妙なことに、VSCode 1.17.2のTypeScript 2.5.3を使用するとエラーが発生します。

クラスSpecialTestのエラー

[ts]
Class 'SpecialTest' incorrectly implements interface 'Test'.
  Types have separate declarations of a private property 'name'.

サンプルコード:

class Test {
    private name: string;

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

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

class SpecialTest implements Test {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

何が欠けていますか?

編集:@fentonが示唆したように、stringの代わりにStringを使用します

13
jvoigt

始める前に、クラスを拡張するときは、extendsキーワードを使用します。クラスを拡張し、インターフェースを実装します。これについては、投稿の下のほうに追加のメモがあります。

class SpecialTest extends Test {

また、stringStringの違いに注意してください。型注釈はほぼ確実にstring(小文字)である必要があります。

そして最後に、コンストラクタパラメータを手動で割り当てる必要がないため、元のパラメータは次のようになります。

class Test {
    private name: string;

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

    // ...
}

次のように表現されます:

class Test {
    constructor(private name: string) {
    }

    // ...
}

これで、問題に対するいくつかのソリューションから選択できます。

保護されたメンバー

nameメンバーを保護すると、サブクラス内で使用できます。

class Test {
    protected name: string;

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

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

class SpecialTest extends Test {
    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

インターフェース

これは私があなたのニーズに最もよく合うと思うソリューションです。

パブリックメンバーをインターフェイスにプルすると、両方のクラスをそのインターフェイスとして扱うことができるはずです(implementsキーワードを明示的に使用するかどうか-TypeScriptは構造的に型指定されます)。

interface SomeTest {
  getName(): string;
  setName(name: string): void;
}

必要に応じて、明示的に実装できます。

class SpecialTest implements SomeTest {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

これで、コードは具象クラスではなくインターフェースに依存するようになります。

クラスをインターフェースとして使用する

技術的にクラスをインターフェースとして参照することは可能ですが、implements MyClassを使用してこれを行う前に問題があります。

まず、将来のあなたも含めて、後でコードを読む必要がある人に不要な複雑さを追加します。また、キーワードに注意する必要があることを意味するパターンを使用しています。 extendsを誤って使用すると、継承されたクラスが変更されたときに将来、トリッキーなバグが発生する可能性があります。メンテナは、どのキーワードが使用されるかについてホークアイになる必要があります。そして何のために?名詞の習慣を構造言語で保存する。

インターフェースは抽象的であり、変更される可能性は低くなります。クラスはより具体的で、変更される可能性が高くなります。クラスをインターフェースとして使用すると、安定した抽象化に依存するという概念全体が台無しになり、代わりに不安定な具象クラスに依存するようになります。

プログラム全体で「インターフェースとしてのクラス」の急増を検討してください。クラスへの変更(たとえば、メソッドを追加するとします)は、不注意で大きな距離のリップルに変化を引き起こす可能性があります...入力に使用されていないメソッドが含まれていないため、プログラムのどの部分が入力を拒否するか?

より良い代替案(アクセス修飾子の互換性の問題がない場合)...

クラスからインターフェースを作成します。

interface MyInterface extends MyClass {
}

または、2番目のクラスで元のクラスをまったく参照しないでください。構造型システムが互換性をチェックできるようにします。

補足:TSLint構成によっては、弱い型(オプションの型のみのインターフェイスなど)がno-empty-interface- Ruleをトリガーします。

プライベートメンバーの特定のケース

これらのいずれも(クラスをインターフェースとして使用する、クラスからインターフェースを生成する、または構造型)プライベートメンバーの問題を解決しません。これが、実際の問題を解決するソリューションが、パブリックメンバーとのインターフェイスを作成することである理由です。

質問のようなプライベートメンバーの特定のケースで、元のパターンを続行するとどうなるかを考えてみましょう。自然な結果として、クラスをインターフェースとして使用するパターンを維持するために、メンバーの可視性が次のように変更されます。

class Test {
    public name: string;

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

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

そして今、私たちはオブジェクト指向のより確立された原則を破っています。

5
Fenton

この例 の親クラスは抽象的であり、インターフェースとして機能します。 TypeScriptインターフェースにはアクセス修飾子がありません。すべてのインターフェースプロパティはパブリックであることが想定されています。

Testのような具象クラスをインターフェイスとして使用すると、実装がプライベートプロパティを実装できず、それらもオーバーライドできないため、問題が発生します。

TestSpecialTestはどちらも、抽象クラスをインターフェースとして実装するか(この場合、抽象クラスはパブリックメンバーのみを持つことができます)、または継承する必要があります(この場合、抽象クラスはprotectedメンバー)。

2
Estus Flask

this にリンクした投稿で、実装の最後の部分を実行しましたか?

> TypeScriptのインターフェイスとしてどのクラスも使用できることに注意してください。したがって、インターフェースと実装を区別する必要が本当にない場合、それは単なる1つの具象クラスである可能性があります。

export class Foo {
    bar() { ... }

    baz() { ... }
 }

...
provider: [Foo]
...

必要に応じて、後でインターフェースとして使用できます。

 export class SomeFoo implements Foo { ... }

 ...
 provider: [{ provide: Foo, useClass: SomeFoo }]
 ...
0
Ian Kirkpatrick

インターフェイスとしてのクラスの使用で@Fentonの説明をフォローアップするには

明示する方法がありますclass SpecialTest implements TestInstanceType<T> ユーティリティタイプ。例えばclass SpecialTest implements InstanceType<typeof Test>。コードを読むときに混乱が少なくなります。

あなた自身のコードでインターフェースを好む、これmagicサードパーティのクラスのデコレータを作成する必要があるときにのみ、私は有用/必要であることがわかりました。

0
kaznovac