在TypeScript中声明抽象方法

我试图弄清楚如何在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方法-它似乎是一个关键字,但没有效果,代码不会编译。

215737 次浏览

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

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

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

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

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

您必须记住在将要实例化的类中进行扩展和实现。它满足了对定义运行时失败代码的关注。如果没有实现接口(例如尝试实例化Animal类),您甚至无法调用会呕吐的方法。我试着让接口扩展下面的BaseAnimal,但它隐藏了构造函数和BaseAnimal的“name”字段。如果我能够做到这一点,那么使用模块和导出就可以防止意外地直接实例化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 < / p > < p >第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;


}

如果您进一步考虑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回答的主要区别是“抽象”基类需要是接口的实现,因此不能单独实例化。

在基类中抛出异常。

protected abstractMethod() {
throw new Error("abstractMethod not implemented");
}
然后你必须在子类中实现。 缺点是没有构建错误,但在运行时。优点是你可以从超类调用这个方法,假设它会工作:)

HTH !

弥尔顿