这是从我的自由和最近出版的摘录书中的打字稿编程语言, 然而,另一个打字稿书 ( https://www.gitbook.com/book/pagalvin/yet-another-typescript-book/details )。
它来自第9章“深度学习”。 这里是:
抽象类
抽象类完善了TypeScript对这种性质的层次结构的支持。 抽象类的外观看起来像是一个标准类,但有一个关键的例外:抽象类可能永远不会被实例化。 如果JavaScript是您的第一和主要编程语言,这似乎很奇怪。 但是,抽象类以及接口使开发人员能够自然而优雅地表达许多常见的软件设计模式。 让我们考虑一个例子。
想象一下,你在写游戏。 玩家在二维地图上放置不同类型的军事基地(例如“陆军”,“海军”)。 基地共享一些共同的特征,例如“名称”,但在重要细节上彼此不同。 陆军基地由士兵组成,海军基地由船组成。 最后,在运行时,玩家可以“**”基地。 这触发了基地在游戏中做出有意义的事情。 这是一种简单的建模方法:
interface Activatable {
ActivateSelf: () => void;
} class NaiveBase {
private _myName: string;
public get Name() { return this._myName; }
constructor (name: string) {
this._myName = name;
}
} class NaiveArmyBase extends NaiveBase implements Activatable{
private _totalSolders: number;
public get TotalSolders() { return this._totalSolders; } constructor(name: string, totalSolders: number) {
super(name);
this._totalSolders = totalSolders;
} public ActivateSelf() {
throw "Not yet implemented";
}
} class NaiveNavyBase extends NaiveBase implements Activatable {
private _totalShips: number;
public get TotalShips() { return this._totalShips; } constructor(name: string, totalShips: number) {
super(name);
this._totalShips = totalShips;
} public ActivateSelf() {
throw "Not yet implemented";
}
} const naiveArmyBase = new NaiveArmyBase("First army base", 100);
const naiveNavyBase = new NaiveNavyBase("First navy base", 3); // This is allowed but makes no sense:
const someOtherBase = new NaiveBase("what kind of base is this?"); 到目前为止,这很简单。 NaiveBase类包含一个私有属性_myName并提供一个get访问器来检索该值。 另外两个类扩展了它并添加了自己的属性: NaiveArmyBase和NaiveNavyBase 。
海军的两个基类都实现了Activatable接口,尽管在此示例中,每个类的ActiveSelf()方法仅引发异常。
这种建模方法存在一个问题:没有像普通香草NaiveBase这样的东西。 玩家从不创建香草基础,而总是创建特定类型的基础。 但是,没有什么可以阻止代码这样做的。
这里还有另一个问题。 这种方法迫使我们在每个类上实现Activatable接口。 我们可以在基类上实现它,但是这只会解决第一个问题-现在我们已经在一个类中实现了一个我们永远都不能实例化的接口。
抽象类为我们解决了这个问题。 这是使用抽象类重写的代码:
interface Activatable {
ActivateSelf: () => void;
} abstract class AbstractBase implements Activatable{
private _myName: string;
public get Name() { return this._myName; } constructor (name: string) {
this._myName = name;
} abstract ActivateSelf(): void;
} class ArmyBase extends AbstractBase {
private _totalSolders: number;
public get TotalSolders() { return this._totalSolders; } constructor(name: string, totalSolders: number) {
super(name);
this._totalSolders = totalSolders;
} public ActivateSelf() {
throw "Not yet implemented";
}
} class NavyBase extends AbstractBase {
private _totalShips: number;
public get TotalShips() { return this._totalShips; } constructor(name: string, totalShips: number) {
super(name);
this._totalShips = totalShips;
} public ActivateSelf() {
throw "Not yet implemented";
}
} const armyBase = new ArmyBase("First army base", 100);
const navyBase = new NavyBase("First navy base", 3);
const anotherArmyBase: Activatable = new ArmyBase("Second army base", 250); // Compiler throws an error - abstract classes can not be instantiated:
const someOtherKindOfBase = new AbstractBase("what kind of base is this?"); 本示例介绍了abstract关键字。 现在,我们有了一个抽象类Base 。 该抽象类实现Activatable接口。 这样,您可以看到TypeScript抽象功能的另一个特征:您可以将类和类成员标记为抽象。 (实际上,如果类抽象包含任何抽象成员,则必须将其标记出来)。 Activatable接口需要ActiveSelf方法。 但是,这种方法仅对“真实”基地(陆军和海军基地)有意义。 因此,我们将ActivateSelf方法本身标记为抽象:
abstract ActivateSelf(): void; 此抽象ActivateSelf方法满足Activatable接口的要求。 这是完美的,因为香草的“基地”无法有效地**自己-只有军队和海军基地才能做到这一点。 同时,它强制子类实现该方法。 这样做有两个好处:
- 您不能忘记这样做,因为IDE和编译器不允许您这样做。
- 由于子类实现了接口,因此我们可以在需要的地方和时间编写利用其类型为
Activatable代码。
抽象的基类显示了另一个功能:抽象的类可以定义非抽象类成员。 由于每个基准都有一个名称,而不管基准的类型如何,因此定义一个具体的_myName属性和关联的getter是有意义的。 子类继承这些具体的类成员(属性和方法),就像它们对具体的类一样。
陆军和海军基地扩展了抽象类,就像它是使用相同的extends关键字的具体类一样。
总结该示例,您可以看到建立陆军和海军基地的工作方式与朴素的示例相同:
const armyBase = new ArmyBase("First army base", 100);
const navyBase = new NavyBase("First navy base", 3); 由于两种类型的基础都实现了Activatable,因此您可以执行以下操作:
const anotherArmyBase: Activatable = new ArmyBase("Second army base", 250);
const activatableNavyBase = <Activatable> navyBase; 让我们将它们放到一个视频中:
From: https://hackernoon.com/abstract-classes-in-typescript-text-video-cda9a4e6a56a