我试图弄清楚如何在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,所以它是类型化的,并有可能覆盖。

另外,我不确定如何正确地定义受保护的方法——它似乎是一个关键字,但没有效果,代码不会编译。


当前回答

不,不,不!当语言不支持该功能时,请不要尝试创建自己的“抽象”类和方法;对于您希望某一特定语言支持的任何语言特性也是如此。在TypeScript中没有正确的方法来实现抽象方法。只要用命名约定来构造代码,这样某些类就永远不会被直接实例化,但不要显式地强制执行这个禁止。

而且,上面的例子只会在运行时提供这种强制,而不是在编译时,正如您在Java/ c#中所期望的那样。

其他回答

在基类中抛出异常。

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

然后你必须在子类中实现。 缺点是没有构建错误,但在运行时。优点是你可以从超类调用这个方法,假设它会工作:)

HTH!

弥尔顿

如果您进一步考虑eric的答案,您实际上可以创建一个相当不错的抽象类实现,它完全支持多态性,并且能够从基类调用实现的方法。让我们从代码开始:

/**
 * 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();
}

这里与eric回答的主要区别是“抽象”基类需要接口的实现,因此不能单独实例化。

我相信接口和基类的组合使用对您来说是可行的。它将在编译时强制执行行为需求(rq_帖子“下面”指的是上面的帖子,而不是这个帖子)。

接口设置了基类无法满足的行为API。您将无法设置基类方法来调用接口中定义的方法(因为如果不定义这些行为,您将无法在基类中实现该接口)。也许有人可以想出一个安全的技巧来允许在父类中调用接口方法。

You have to remember to extend and implement in the class you will instantiate. It satisfies concerns about defining runtime-fail code. You also won't even be able to call the methods that would puke if you haven't implemented the interface (such as if you try to instantiate the Animal class). I tried having the interface extend the BaseAnimal below, but it hid the constructor and the 'name' field of BaseAnimal from Snake. If I had been able to do that, the use of a module and exports could have prevented accidental direct instantiation of the BaseAnimal class.

把这个粘贴到这里,看看它是否适合你: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

Part 2: I had another case where I wanted an abstract class, but I was prevented from using my solution above, because the defined methods in the "abstract class" needed to refer to the methods defined in the matching interface. So, I tool FristvanCampen's advice, sort of. I have the incomplete "abstract" class, with method implementations. I have the interface with the unimplemented methods; this interface extends the "abstract" class. I then have a class that extends the first and implements the second (it must extend both because the super constructor is inaccessible otherwise). See the (non-runnable) sample below:

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;
    }
}

and

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;

}

不,不,不!当语言不支持该功能时,请不要尝试创建自己的“抽象”类和方法;对于您希望某一特定语言支持的任何语言特性也是如此。在TypeScript中没有正确的方法来实现抽象方法。只要用命名约定来构造代码,这样某些类就永远不会被直接实例化,但不要显式地强制执行这个禁止。

而且,上面的例子只会在运行时提供这种强制,而不是在编译时,正如您在Java/ c#中所期望的那样。

name属性被标记为受保护。这是在TypeScript 1.3中添加的,现在已经牢固地建立起来了。

makeSound方法被标记为抽象方法,类也是如此。您现在不能直接实例化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);
    }
}