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。这是在 TypeScript 1.3 中添加的,现在已经牢固确立。

makeSound方法被标记为abstract,类也是如此。你不能直接实例化一个Animalnow,因为它是抽象的。这是 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);
    }
}
于 2012-11-11T21:28:15.317 回答
24

如果您进一步了解 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();
}

这里与 Erics 答案的主要区别在于“抽象”基类需要接口的实现,因此不能自行实例化。

于 2014-05-02T10:12:44.993 回答
3

我相信结合使用接口和基类可以为您工作。它将在编译时强制执行行为要求(rq_ post "below" 指的是上面的帖子,不是这个)。

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

您必须记住在您将实例化的类中扩展和实现。它满足了对定义运行时失败代码的关注。如果您还没有实现接口(例如,如果您尝试实例化 Animal 类),您甚至无法调用会呕吐的方法。我尝试让接口扩展下面的 BaseAnimal,但它对 Snake 隐藏了 BaseAnimal 的构造函数和“名称”字段。如果我能够做到这一点,那么使用模块和导出可以防止 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 的建议。我有不完整的“抽象”类,带有方法实现。我有未实现方法的接口;这个接口扩展了“抽象”类。然后我有一个扩展第一个并实现第二个的类(它必须扩展两者,因为超级构造函数否则无法访问)。请参阅下面的(不可运行的)示例:

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 回答