230

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

5 に答える 5

339

nameプロパティはとしてマークされていますprotected。これはTypeScript1.3で追加され、現在はしっかりと確立されています。

クラスと同様に、makeSoundメソッドはとしてマークされabstractます。Animal抽象的であるため、nowを直接インスタンス化することはできません。これはTypeScript1.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);
    }
}
于 2012-11-11T21:28:15.317 に答える
24

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のインスタンスを渡す必要があることに注意してください。例えば:IAnimalAnimal

//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の回答との主な違いは、「抽象」基本クラスにはインターフェイスの実装が必要であるため、それ自体でインスタンス化できないことです。

于 2014-05-02T10:12:44.993 に答える
3

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

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

インスタンス化するクラスを拡張して実装することを忘れないでください。これは、ランタイム失敗コードの定義に関する懸念を満たします。また、インターフェイスを実装していない場合(Animalクラスをインスタンス化しようとした場合など)、pukeするメソッドを呼び出すこともできません。以下の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の答えに出くわしました。彼は、抽象クラスはアンチパターンであり、実装クラスの注入されたインスタンスを使用して、1つのインスタンス化されたベース「抽象」クラスを提案します。これは公正ですが、反論があります。自分で読んでください: https ://typescript.codeplex.com/discussions/449920

パート2:抽象クラスが必要な別のケースがありましたが、「抽象クラス」で定義されたメソッドが一致するインターフェイスで定義されたメソッドを参照する必要があるため、上記のソリューションを使用できませんでした。だから、私はFristvanCampenのアドバイスを使っています。メソッドの実装を含む、不完全な「abstract」クラスがあります。実装されていないメソッドとのインターフェースがあります。このインターフェースは「abstract」クラスを拡張します。次に、最初のクラスを拡張し、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;

}
于 2014-03-18T23:37:09.090 に答える
1

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

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

次に、サブクラスに実装する必要があります。短所は、ビルドエラーはありませんが、実行時に発生することです。長所は、このメソッドが機能すると仮定して、スーパークラスからこのメソッドを呼び出すことができることです:)

HTH!

ミルトン

于 2015-08-13T17:52:03.527 に答える
-23

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

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

于 2013-12-16T20:07:24.823 に答える