在 TypeScript 中声明抽象方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13333489/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 00:56:13  来源:igfitidea点击:

Declaring abstract method in TypeScript

typescript

提问by Vojtěch

I am trying to figure out how to correctly define abstract methods in TypeScript:

我想弄清楚如何在 TypeScript 中正确定义抽象方法:

Using the original inheritance example:

使用原始继承示例:

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

I would like to know how to correctly define method makeSound, so it is typed and possible to overried.

我想知道如何正确定义 makeSound 方法,因此它被输入并且可能被覆盖。

Also, I am not sure how to define correctly protectedmethods - it seems to be a keyword, but has no effect and the code won't compile.

另外,我不确定如何正确定义protected方法 - 它似乎是一个关键字,但没有任何作用,代码将无法编译。

回答by Fenton

The nameproperty is marked as protected. This was added in TypeScript 1.3 and is now firmly established.

name属性标记为protected。这是在 TypeScript 1.3 中添加的,现在已经牢固确立。

The makeSoundmethod is marked as abstract, as is the class. You cannot directly instantiate an Animalnow, because it is abstract. This is part of TypeScript 1.6, which is now officially live.

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

The old way of mimicking an abstract method was to throw an error if anyone used it. You shouldn't need to do this any more once TypeScript 1.6 lands in your project:

模仿抽象方法的旧方法是在有人使用它时抛出错误。一旦 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);
    }
}

回答by Tiddo

If you take Erics answer a little further you can actually create a pretty decent implementation of abstract classes, with full support for polymorphism and the ability to call implemented methods from the base class. Let's start with the code:

如果您更深入地了解 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"

Since the Animalclass requires an implementation of IAnimalit's impossible to construct an object of type Animalwithout having a valid implementation of the abstract methods. Note that for polymorphism to work you need to pass around instances of IAnimal, not Animal. E.g.:

由于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();
}

The main difference here with Erics answer is that the "abstract" base class requiresan implementation of the interface, and thus cannot be instantiated on it's own.

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

回答by Eric

I believe that using a combination of interfaces and base classes could work for you. It will enforce behavioral requirements at compile time (rq_ post "below" refers to a post above, which is not this one).

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

The interface sets the behavioral API that isn't met by the base class. You will not be able to set base class methods to call on methods defined in the interface (because you will not be able to implement that interface in the base class without having to define those behaviors). Maybe someone can come up with a safetrick to allow calling of the interface methods in the parent.

该接口设置基类不满足的行为 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.

你必须记住在你将实例化的类中扩展和实现。它满足了对定义运行时失败代码的担忧。如果您还没有实现接口(例如,如果您尝试实例化 Animal 类),您甚至将无法调用会呕吐的方法。我尝试让接口扩展下面的 BaseAnimal,但它从 Snake 中隐藏了 BaseAnimal 的构造函数和“名称”字段。如果我能够做到这一点,使用模块和导出可以防止 BaseAnimal 类的意外直接实例化。

Paste this in here to see if it works for you: http://www.typescriptlang.org/Playground/

将其粘贴到此处以查看它是否适合您: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);

I came across FristvanCampen's answer as linked below. He says abstract classes are an anti-pattern, and suggests that one instantiate base 'abstract' classes using an injected instance of an implementing class. This is fair, but there are counter arguments made. Read for yourself: https://typescript.codeplex.com/discussions/449920

我遇到了 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:

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

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;

}

回答by Milton

I use to throw an exception in the base class.

我过去常常在基类中抛出异常。

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

Then you have to implement in the sub-class. The cons is that there is no build error, but run-time. The pros is that you can call this method from the super class, assuming that it will work :)

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

HTH!

哼!

Milton

米尔顿

回答by rq_

No, no, no!Please do not try to make your own 'abstract' classes and methods when the language does not support that feature; the same goes for any language feature you wish a given language supported. There is no correct way to implement abstract methods in TypeScript. Just structure your code with naming conventions such that certain classes are never directly instantiated, but without explicitly enforcing this prohibition.

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

Also, the example above is only going to provide this enforcement at run time, NOT at compile time, as you would expect in Java/C#.

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