前言
关键字
抽象类、抽象成员、abstract、模板模式
参考资料
- 「百度文库」中国象棋规则英文版 Chinese Chess:链接
notes
抽象类
抽象类
含义:仅表示一个抽象的概念,用于提取子类共有的成员
特点:不能实例化
实例化
在面向对象的编程中,把用类创建对象的过程称为实例化。
abstract
在类名前面加上 abstract
关键字,表示该类是一个抽象类。
class Chess {}
// 马
class Knight extends Chess {}
// 炮
class cannon extends Chess {}
// 兵
class Pawn extends Chess {}
const knight1 = new Knight();
const cannon1 = new cannon();
const pawn1 = new Pawn();
const chess1 = new Chess();
**const chess1 = new Chess()**
Chess
应该是一个抽象类,这么写应该是不被允许的,在 TS 代码中,我们可以通过 abstract 关键字来限定 Chess 是一个抽象类。
abstract class Chess {}
// 马
class Knight extends Chess {}
// 炮
class cannon extends Chess {}
// 兵
class Pawn extends Chess {}
const knight1 = new Knight();
const cannon1 = new cannon();
const pawn1 = new Pawn();
const chess1 = new Chess(); // ×
**abstract class Chess {}**
使用 abstract 关键字对 Chess 类进行约束,表示它是一个抽象类,不能被实例化。
抽象成员
父类中,可能知道有些成员是必须存在的,但是不知道该成员的值或实现是什么。
因此,需要有一种强约束,让继承该类的子类,必须要实现该成员。
实现:给成员赋值
抽象成员
抽象成员只能出现在抽象类中
这些抽象成员必须在子类中实现
abstract class Chess {
x: number = 0;
y: number = 0;
abstract readonly name: string;
}
class Knight extends Chess {
readonly name: string = "马";
}
class cannon extends Chess {
readonly name: string;
constructor() {
super();
this.name = "炮";
}
}
class Pawn extends Chess {
get name() {
return "兵";
}
}
const ma = new Knight();
const pao = new cannon();
const bing = new Pawn();
**get name() { ... }**
只定义 get,未定义 set,相当于写了只读修饰符 readonly
abstract class Chess {
x: number = 0;
y: number = 0;
abstract readonly name: string;
abstract move(targetX: number, targetY: number): boolean;
}
class Knight extends Chess {
readonly name: string = "马";
move(targetX: number, targetY: number): boolean {
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
class cannon extends Chess {
readonly name: string;
constructor() {
super();
this.name = "炮";
}
move(targetX: number, targetY: number): boolean {
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
class Pawn extends Chess {
get name() {
return "兵";
}
move(targetX: number, targetY: number): boolean {
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
const ma = new Knight();
const pao = new cannon();
const bing = new Pawn();
设计模式「模板模式」
设计模式
面对一些常见的功能场景,有一些固定的、经过多年实践的成熟方法,这些方法称之为设计模式。
即:针对一些常见问题,程序员们给出的成熟解决方案。
模板模式
有些方法,所有子类实现的流程完全一致,只是流程中的某个步骤的具体实现不一致,可以将该方法提取到父类,在父类中完成整个流程的实现,遇到实现不一致的方法时,将该方法做成抽象类。
abstract class Chess {
x: number = 0;
y: number = 0;
abstract readonly name: string;
abstract move(targetX: number, targetY: number): boolean;
}
class Knight extends Chess {
readonly name: string = "马";
move(targetX: number, targetY: number): boolean {
console.log('1. 边界判断');
console.log('2. 目标位置是否存在己方棋子');
console.log('3. 棋子移动规则判断');
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
class cannon extends Chess {
readonly name: string;
constructor() {
super();
this.name = "炮";
}
move(targetX: number, targetY: number): boolean {
console.log('1. 边界判断');
console.log('2. 目标位置是否存在己方棋子');
console.log('3. 棋子移动规则判断');
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
class Pawn extends Chess {
get name() {
return "兵";
}
move(targetX: number, targetY: number): boolean {
console.log('1. 边界判断');
console.log('2. 目标位置是否存在己方棋子');
console.log('3. 棋子移动规则判断');
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
const ma = new Knight();
const pao = new cannon();
const bing = new Pawn();
存在的问题:目前的写法,存在大量的公共代码
目标:为了让我们的代码更加优雅,我们应该采用合适的方式将这些公共逻辑提取出来
abstract class Chess {
x: number = 0;
y: number = 0;
abstract readonly name: string;
abstract move(targetX: number, targetY: number): boolean;
protected isOutSide(): boolean {
console.log("1. 边界判断");
return false;
}
protected targetHasAlly(): boolean {
console.log("2. 目标位置是否存在己方棋子");
return false;
}
protected finishMove(targetX: number, targetY: number): boolean {
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
}
class Knight extends Chess {
readonly name: string = "马";
move(targetX: number, targetY: number): boolean {
this.isOutSide();
this.targetHasAlly();
console.log("3. 棋子移动规则判断");
return this.finishMove(targetX, targetY);
}
}
class cannon extends Chess {
readonly name: string;
constructor() {
super();
this.name = "炮";
}
move(targetX: number, targetY: number): boolean {
this.isOutSide();
this.targetHasAlly();
console.log("3. 棋子移动规则判断");
return this.finishMove(targetX, targetY);
}
}
class Pawn extends Chess {
get name() {
return "兵";
}
move(targetX: number, targetY: number): boolean {
this.isOutSide();
this.targetHasAlly();
console.log("3. 棋子移动规则判断");
return this.finishMove(targetX, targetY);
}
}
const ma = new Knight();
const pao = new cannon();
const bing = new Pawn();
可以先将公共逻辑提取到父类中
然后通过继承的方式在子类中调用这些公共逻辑
新的问题:
- 出现大量重复调用函数的代码(次要)
- 调用函数的顺序和方式,没有强约束,很容易出错(主要)
下面介绍如何使用模板模式来优化上述程序。
abstract class Chess {
x: number = 0;
y: number = 0;
abstract readonly name: string;
move(targetX: number, targetY: number): boolean {
this._isOutSide();
this._targetHasAlly();
if (!this.rule(targetX, targetY)) return false;
this._finishMove(targetX, targetY);
return true;
}
private _isOutSide(): boolean {
console.log("1. 边界判断");
return false;
}
private _targetHasAlly(): boolean {
console.log("2. 目标位置是否存在己方棋子");
return false;
}
private _finishMove(targetX: number, targetY: number): boolean {
this.x = targetX;
this.y = targetY;
console.log(`${this.name}移动成功`);
return true;
}
protected abstract rule(targetX: number, targetY: number): boolean;
}
class Knight extends Chess {
protected rule(targetX: number, targetY: number): boolean {
console.log("3. 棋子移动规则判断");
return true;
}
readonly name: string = "马";
}
class cannon extends Chess {
protected rule(targetX: number, targetY: number): boolean {
console.log("3. 棋子移动规则判断");
return true;
}
readonly name: string;
constructor() {
super();
this.name = "炮";
}
}
class Pawn extends Chess {
protected rule(targetX: number, targetY: number): boolean {
console.log("3. 棋子移动规则判断");
return true;
}
get name() {
return "兵";
}
}
const ma = new Knight();
const pao = new cannon();
const bing = new Pawn();
**rule**
抽象方法 rule 是唯一不同的逻辑,这部分需要由子类来实现。
上面这种写法对于后续的维护,帮助是很大的。
模拟场景:上述的 Chess 是很早之前写的一个类,现在接到个需求,得新建一个 King「将」类。根据语义,不难得知 King 应该是继承自 Chess 的。接下来我们只要写 class King extends Chess { }
,然后点击「快速修复」,vscode 编辑器就能快速帮我们生成必须要实现的内容。
class King extends Chess {
name: string = "将";
protected rule(targetX: number, targetY: number): boolean {
console.log("3. 棋子移动规则判断");
return true;
}
}