我试图弄清楚如何在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
方法 - 它似乎是一个关键字,但没有效果,代码也无法编译。
答案 0 :(得分:229)
name
属性标记为protected
。这是在TypeScript 1.3中添加的,现在已牢固确立。
makeSound
方法标记为abstract
,类也是如此。您现在无法直接实例化Animal
,因为它是抽象的。 This is part of 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);
}
}
答案 1 :(得分: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
。 E.g:
//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答案的主要区别在于“抽象”基类需要接口的实现,因此无法在其自身实例化。
答案 2 :(得分:2)
我相信使用接口和基类的组合可能对您有用。它将在编译时强制执行行为要求(rq_ post“下面”指的是上面的帖子,而不是这个帖子。)
接口设置基类不满足的行为API。您将无法设置基类方法来调用接口中定义的方法(因为您无法在基类中实现该接口而无需定义这些行为)。也许有人可以提出安全技巧来允许调用父级中的接口方法。
您必须记住在要实例化的类中扩展和实现。它满足了关于定义运行时失败代码的顾虑。如果你没有实现接口(例如,如果你试图实例化Animal类),你甚至也不能调用那些会呕吐的方法。我尝试让接口扩展下面的BaseAnimal,但它隐藏了构造函数和来自Snake的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
第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;
}
答案 3 :(得分:1)
我用来在基类中抛出异常。
test_list = malloc(sizeof(node_t));
head->val = 5; //some value
head->next = NULL;
push(test_list, 1);
...
然后你必须在子类中实现。 缺点是没有构建错误,但运行时间。优点是你可以从超类中调用这个方法,假设它可以工作:)
HTH!
米尔顿
答案 4 :(得分:-14)
不,不,不! 当语言不支持该功能时,请不要尝试制作自己的“抽象”类和方法;您希望支持给定语言的任何语言功能都是如此。没有正确的方法在TypeScript中实现抽象方法。只需使用命名约定构建代码,以便某些类永远不会直接实例化,但不会明确强制执行此禁止。
此外,上面的示例仅在运行时提供此强制执行,而不是在编译时提供,正如您在Java / C#中所期望的那样。