クラスを別のクラスのインターフェイスとして使用しようとしました。これで、テスト用の最新の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
を使用します
始める前に、クラスを拡張するときは、extends
キーワードを使用します。クラスを拡張し、インターフェースを実装します。これについては、投稿の下のほうに追加のメモがあります。
class SpecialTest extends Test {
また、string
とString
の違いに注意してください。型注釈はほぼ確実に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;
}
}
そして今、私たちはオブジェクト指向のより確立された原則を破っています。
この例 の親クラスは抽象的であり、インターフェースとして機能します。 TypeScriptインターフェースにはアクセス修飾子がありません。すべてのインターフェースプロパティはパブリックであることが想定されています。
Test
のような具象クラスをインターフェイスとして使用すると、実装がプライベートプロパティを実装できず、それらもオーバーライドできないため、問題が発生します。
Test
とSpecialTest
はどちらも、抽象クラスをインターフェースとして実装するか(この場合、抽象クラスはパブリックメンバーのみを持つことができます)、または継承する必要があります(この場合、抽象クラスはprotected
メンバー)。
this にリンクした投稿で、実装の最後の部分を実行しましたか?
> TypeScriptのインターフェイスとしてどのクラスも使用できることに注意してください。したがって、インターフェースと実装を区別する必要が本当にない場合、それは単なる1つの具象クラスである可能性があります。
export class Foo { bar() { ... } baz() { ... } } ... provider: [Foo] ...
必要に応じて、後でインターフェースとして使用できます。
export class SomeFoo implements Foo { ... } ... provider: [{ provide: Foo, useClass: SomeFoo }] ...
インターフェイスとしてのクラスの使用で@Fentonの説明をフォローアップするには
明示する方法がありますclass SpecialTest implements Test
InstanceType<T>
ユーティリティタイプ。例えばclass SpecialTest implements InstanceType<typeof Test>
。コードを読むときに混乱が少なくなります。
あなた自身のコードでインターフェースを好む、これmagicサードパーティのクラスのデコレータを作成する必要があるときにのみ、私は有用/必要であることがわかりました。