在TypeScript中是否有类似于"sealed"或"final"的等效功能?

60

我正在尝试在一个超类中实现一个方法,这个方法应该可以在子类中使用,但不能被修改。考虑以下代码:

export abstract class BaseClass {
    universalBehavior(): void {
        doStuff(); // Do some universal stuff the same way in all sub classes
        specializedBehavior(); // Delegate specialized stuff to sub classes
    }

    protected abstract specializedBehavior(): void;
}

我的意图是,BaseClass的任何子类不仅可以省略对universalBehavior()的实现,甚至不允许提供实现。在TypeScript中还不能做到这一点吗?当我在子类中省略实现时,Intellisense会发出警告。我似乎只能做到这一点:

export class SubClass extends BaseClass {
    universalBehavior(): void {
        super.universalBehavior();
    }

    specializedBehavior(): void {
        // sub class' implementation
    }
}
显然,这是有问题的,因为我必须确保没有任何子类使用universalBehavior()方法去调用其他不同于super.universalBehavior()的东西。

1
这是有关在TypeScript中添加final的讨论:https://github.com/Microsoft/TypeScript/issues/8306 - Heretic Monkey
@MikeMcCaughan - 所以我猜这里没有关键字。或许有人会发布某种解决方法。 - bubbleking
我见过使用@readonly装饰器,该装饰器将在对象上使用 Object.freeze,但那不是同一件事情(它禁止对类进行任何修改)。 - Heretic Monkey
在JavaScript中,这个问题通常通过直接在任何子类上调用原型方法来解决,而不是利用原型链。一个经常可以看到的例子是 Object.hasOwnProperty.call(target, "some-property"),用于检查属性是否来自对象本身而不是原型链。你可以使用相同的技巧,即使子类遮蔽了这个方法也没有关系。 - weaknespase
3个回答

37

33

实现“密封方法”作为只读函数类型属性的黑科技示例,当尝试在继承类中覆盖该属性时,会抛出编译器错误:

abstract class BaseClass {
    protected element: JQuery<HTMLElement>;
    constructor(element: JQuery<HTMLElement>) {
        this.element = element;
    }
    readonly public dispose = (): void => {
        this.element.remove();
    }
}

class MyClass extends BaseClass {
    constructor(element: JQuery<HTMLElement>) {
        super(element);
    }
    public dispose(): void { } // Compiler error: "Property 'dispose' in type 'MyClass' is not assignable to the same property in base type 'BaseClass'"
}

TypeScript 2.0通过使用私有构造函数来支持"final"类:

TypeScript 2.0支持使用私有构造函数实现"final"类:

class A {
    private constructor(){}
}

class B extends A{} //Cannot extend a class 'A'. Class constructor is marked as private.ts(2675)

4
好的捕捉!我甚至不确定这是否算是黑客攻击。在我看来,它看起来完全有效和符合预期。 - Eric MORAND
2
@weaknespase 我认为编译器将Typescript转换为Javascript的方式已经改变了,因为将上述代码放入Typescript Playground中,现在出现的错误是“类'BaseClass'定义了实例成员属性'dispose',但扩展类'MyClass'将其定义为实例成员函数。”这意味着绕过它非常容易 - 对于“密封方法”来说不是一个可靠的解决方法。 - Asher Garland
9
这不是一个有效的解决方案。如果子类也使用了 lambda 属性,TypeScript 将不会发出警告。 - lorefnon
7
但是你不能创建一个 new A()...所以它并不完全等同于其他语言中的 final 类。 - Andy
1
你仍然可以创建一个私有构造函数的类对象。唯一需要做的是创建一个公共静态包装方法来调用私有构造函数。public static readonly create= () => new this(); - dev.dmtrllv
显示剩余2条评论

-1
// I use this workaround:

export default class CreateHandler extends BaseHandler {
    // final prop used as method
    public readonly create = (blueprint: Blueprint): Response<Record> => {
        return blueprint.create();
    };

    // normal method
    public store(blueprint: Blueprint): Response<Record> {
        return this.response(blueprint.create());
    }
}

3
请在发表答案时包含代码的解释。 - Brian Lee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接