TypeScriptで抽象メソッドを宣言する


195

TypeScriptで抽象メソッドを正しく定義する方法を理解しようとしています。

元の継承の例を使用:

class Animal {
    constructor(public name) { }
    makeSound(input : string) : string;
    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name) { super(name); }
    makeSound(input : string) : string {
        return "sssss"+input;
    }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

makeSoundメソッドを正しく定義する方法を知りたいので、型指定してオーバーライドできます。

また、protectedメソッドを正しく定義する方法がわかりません-キーワードのようですが、効果がなく、コードはコンパイルされません。


4
抽象クラスと抽象メソッドは、今後のTypeScript 1.6の新機能です。
— falconepl 2015

回答:


284

nameプロパティは次のように記されていますprotected。これはTypeScript 1.3で追加され、現在はしっかりと確立されています。

makeSoundこの方法は、次のようにマークされているabstractクラスであるとして、。Animal抽象的であるため、現在を直接インスタンス化することはできません。これは現在正式に公開されているTypeScript 1.6の一部です。

abstract class Animal {
    constructor(protected name: string) { }

    abstract makeSound(input : string) : string;

    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }

    makeSound(input : string) : string {
        return "sssss"+input;
    }

    move() {
        alert("Slithering...");
        super.move(5);
    }
}

抽象メソッドを模倣する古い方法は、誰かがそれを使用した場合にエラーをスローすることでした。TypeScript 1.6がプロジェクトに導入されたら、これを行う必要はありません。

class Animal {
    constructor(public name) { }
    makeSound(input : string) : string {
        throw new Error('This method is abstract');
    }
    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name) { super(name); }
    makeSound(input : string) : string {
        return "sssss"+input;
    }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

抽象メソッドをオーバーライドするときに、パラメーターを見逃したり、パラメーターの型を変更したり、戻り値の型を変更したりしても、コンパイラーが文句を言わないのは通常の動作ですか?
— Vetterjack 2017

1
パラメーターを省略することは有効で(使用しない場合は、渡される値を無視できます)、互換性のあるタイプのパラメーターを持つことができます。makeSound(input : number) : string {上記の例に基づいて、抽象メソッドを実装しようとすると、エラーが発生inputします。Type 'string' is not assignable to type 'number'.。
— フェントン2017

19

Ericsの回答をもう少し詳しく見ると、ポリモーフィズムを完全にサポートし、実装されたメソッドを基本クラスから呼び出す機能を備えた、抽象クラスのかなりまともな実装を実際に作成できます。コードから始めましょう:

/**
 * The interface defines all abstract methods and extends the concrete base class
 */
interface IAnimal extends Animal {
    speak() : void;
}

/**
 * The abstract base class only defines concrete methods & properties.
 */
class Animal {

    private _impl : IAnimal;

    public name : string;

    /**
     * Here comes the clever part: by letting the constructor take an 
     * implementation of IAnimal as argument Animal cannot be instantiated
     * without a valid implementation of the abstract methods.
     */
    constructor(impl : IAnimal, name : string) {
        this.name = name;
        this._impl = impl;

        // The `impl` object can be used to delegate functionality to the
        // implementation class.
        console.log(this.name + " is born!");
        this._impl.speak();
    }
}

class Dog extends Animal implements IAnimal {
    constructor(name : string) {
        // The child class simply passes itself to Animal
        super(this, name);
    }

    public speak() {
        console.log("bark");
    }
}

var dog = new Dog("Bob");
dog.speak(); //logs "bark"
console.log(dog instanceof Dog); //true
console.log(dog instanceof Animal); //true
console.log(dog.name); //"Bob"

以来Animalクラスの実装が必要ですIAnimal、それはタイプのオブジェクトを構築することは不可能だAnimal抽象メソッドの有効な実装を持たずに。ポリモーフィズムが機能するためにはIAnimal、ではなくのインスタンスを渡す必要があることに注意してくださいAnimal。例えば:

//This works
function letTheIAnimalSpeak(animal: IAnimal) {
    console.log(animal.name + " says:");
    animal.speak();
}
//This doesn't ("The property 'speak' does not exist on value of type 'Animal')
function letTheAnimalSpeak(animal: Animal) {
    console.log(animal.name + " says:");
    animal.speak();
}

ここでのEricsの回答との主な違いは、「抽象」基本クラスはインターフェースの実装を必要とするため、それ自体ではインスタンス化できないということです。


1
私にとっては、少なくともTypescript v1の場合-コンストラクター内から 'this'を参照してスーパーに渡すことはできません。考え?
— キーランベントン2014年

使用しているコンパイラの正確なバージョンと、どのエラーが発生しますか?tsc 1.0.1は、上記のスニペットを完全にコンパイルします。
— Tiddo 2014年

'this'キーワードは、super()では使用できません。Imはtsc 1.0.3を使用
— Zasz

それはかなり奇妙です。CLIコンパイラーまたはVisual Studioを使用していますか?
— Tiddo 2014年

私も、super()呼び出しで「this」を使用できません。すぐに使用して、親のメンバーを子の実装に設定できますが、抽象クラスの拡張は強制されません。PalantirのEclispe Typsscriptプラグイン、v1.0.1を使用しています。super(this)がtypescriptlang.org/Playgroundで正常に動作することに気づきました。
— Eric

2

インターフェースと基本クラスの組み合わせを使用するとうまくいくと思います。コンパイル時に動作要件を適用します(rq_ post "below"は上の投稿を指しますが、これはこれではありません)。

インターフェースは、基本クラスが満たしていない動作APIを設定します。インターフェイスで定義されたメソッドを呼び出すように基本クラスメソッドを設定することはできません(これらの動作を定義する必要がないため、基本クラスでそのインターフェイスを実装することができないため)。たぶん、誰かが親のインターフェースメソッドの呼び出しを許可するための安全なトリックを考え出すことができます。

インスタンス化するクラスで拡張および実装することを忘れないでください。ランタイム失敗コードの定義に関する懸念を満たします。また、インターフェイスを実装していない場合(Animalクラスをインスタンス化しようとした場合など)は、起動するメソッドを呼び出すこともできません。インターフェースで以下のBaseAnimalを拡張してみましたが、BaseAnimalのコンストラクターと 'name'フィールドがSnakeから隠されていました。もしそれができたなら、モジュールとエクスポートを使用することで、BaseAnimalクラスの偶発的な直接インスタンス化を防ぐことができたでしょう。

これをここに貼り付けて、機能するかどうかを確認します。http://www.typescriptlang.org/Playground/

// The behavioral interface also needs to extend base for substitutability
interface AbstractAnimal extends BaseAnimal {
    // encapsulates animal behaviors that must be implemented
    makeSound(input : string): string;
}

class BaseAnimal {
    constructor(public name) { }

    move(meters) {
        alert(this.name + " moved " + meters + "m.");
    }
}

// If concrete class doesn't extend both, it cannot use super methods.
class Snake extends BaseAnimal implements AbstractAnimal {
    constructor(name) { super(name); }
    makeSound(input : string): string {
        var utterance = "sssss"+input;
        alert(utterance);
        return utterance;
    }
    move() {
        alert("Slithering...");
        super.move(5);
    }
}

var longMover = new Snake("windy man");

longMover.makeSound("...am I nothing?");
longMover.move();

var fulture = new BaseAnimal("bob fossil");
// compile error on makeSound() because it is not defined.
// fulture.makeSound("you know, like a...")
fulture.move(1);

以下にリンクされているように、FristvanCampenの答えに出くわしました。彼は、抽象クラスはアンチパターンであり、実装クラスの注入されたインスタンスを使用して、ベースの「抽象」クラスをインスタンス化することを提案しています。これは公平ですが、反対意見が出されています。ご自身でお読みください:https : //typescript.codeplex.com/discussions/449920

パート2:抽象クラスが必要な別のケースがありましたが、「抽象クラス」で定義されたメソッドは、一致するインターフェースで定義されたメソッドを参照する必要があるため、上記のソリューションを使用できませんでした。それで、私はFristvanCampenのアドバイスを整理します。メソッドの実装を含む不完全な「抽象」クラスがあります。実装されていないメソッドとのインターフェースがあります。このインターフェースは「抽象」クラスを拡張します。次に、最初のクラスを拡張して2番目のクラスを実装するクラスがあります(それ以外の場合は、スーパーコンストラクターにアクセスできないため、両方を拡張する必要があります)。以下の(実行不可能な)サンプルを参照してください。

export class OntologyConceptFilter extends FilterWidget.FilterWidget<ConceptGraph.Node, ConceptGraph.Link> implements FilterWidget.IFilterWidget<ConceptGraph.Node, ConceptGraph.Link> {

    subMenuTitle = "Ontologies Rendered"; // overload or overshadow?

    constructor(
        public conceptGraph: ConceptGraph.ConceptGraph,
        graphView: PathToRoot.ConceptPathsToRoot,
        implementation: FilterWidget.IFilterWidget<ConceptGraph.Node, ConceptGraph.Link>
        ){
        super(graphView);
        this.implementation = this;
    }
}

そして

export class FilterWidget<N extends GraphView.BaseNode, L extends GraphView.BaseLink<GraphView.BaseNode>> {

    public implementation: IFilterWidget<N, L>

    filterContainer: JQuery;

    public subMenuTitle : string; // Given value in children

    constructor(
        public graphView: GraphView.GraphView<N, L>
        ){

    }

    doStuff(node: N){
        this.implementation.generateStuff(thing);
    }

}

export interface IFilterWidget<N extends GraphView.BaseNode, L extends GraphView.BaseLink<GraphView.BaseNode>> extends FilterWidget<N, L> {

    generateStuff(node: N): string;

}

1

基本クラスで例外をスローするために使用します。

protected abstractMethod() {
    throw new Error("abstractMethod not implemented");
}

次に、サブクラスで実装する必要があります。短所は、ビルドエラーがないことですが、ランタイムです。長所は、スーパークラスからこのメソッドを呼び出すことができることです。

HTH!

ミルトン


-20

ダメダメダメ!言語がその機能をサポートしていない場合は、独自の「抽象」クラスおよびメソッドを作成しないでください。同じことが、特定の言語をサポートしたい言語機能にも当てはまります。TypeScriptに抽象メソッドを実装する正しい方法はありません。特定のクラスが直接インスタンス化されないように、ただしこの禁止を明示的に強制しないように、命名規則を使用してコードを構造化するだけです。

また、上記の例は、Java / C#で期待されるように、コンパイル時にではなく、実行時にのみこの強制を提供します。


4
あなたがどこから来たのかはわかりますが、私は敬遠します。言語が何かを実装する場合、自分で再実装することは悪いことです。しかし、何かがない場合は、自分で実装するしかありません。確かに、実行時まで問題は表示されませんが、何かを初めてテストするときに例外をスローすると、すぐに失敗したことがわかります。もちろん、これは理想的ではありません。IMO、Typescriptが抽象クラスのサポートを必要とするのはそのためです。しかしそれができるまで...
— Maverick

私はJavaScriptにクラス、型推論、静的型付けとインターフェイスがあり、Typescriptにそれがあることを望みました。抽象メソッドの場合も同様です。コンパイラは、抽象クラスを拡張するクラスが抽象メソッドを実装していることを確認するだけです。これは、インターフェイスに対して既に行われているものです(インターフェイスは、基本的に抽象メソッドのみのクラスです)
— Tony BenBrahim

1
ここで@rq_に同意する傾向があります。抽象メソッドのポイントは、プログラムが無効な状態にならないようにするコンパイル時の検証を取得することです。提案されたソリューションは、実行時チェックを提供するだけです。つまり、プログラムの実行時に、プログラムが有効な状態であるかどうかを確認することはできません。つまり、メソッドが実装されていないことを前提に操作し、それに応じて保護する必要があります。抽象メソッドを持っていると自分に嘘をつくことは、予期しない実行時の動作に噛まれるように求めているだけです。
— Micah Zoltu 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.