要点

  • 子类是 extends 父类出来的

  • 子类会集成父类所有 public 类型的实例变量和方法,但不会集成父类所有 private 类型的变量和方法

  • 继承下来的属性可以被覆盖掉,但实例变量不能被覆盖掉

  • 类的多态性可以从方法override 和 overload 来体现

  • 一个类可以继承另外一个类或者实现多个接口



重写 Override


工程意义:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!


如果需要扩展行为,就可以通过子类来实现,因此,继承是给类扩展和添加行为时的唯一工具。从继承开始说起。

type Rect = {
x: number,
y: number
}

class Animal {
protected flegCount:number;
protected isAlive:boolean;
protected color:string;
protected position: Rect;

constructor(){
this.flegCount = 4;
this.isAlive = true;
this.color = "white"

this.position = {
x: 0,
y: 0
}
}

speak():string|null {
return null;
}
}

class Dog extends Animal {

speak():string|null {
return "旺旺旺!";
}
}

class Cat extends Animal {
speak():string|null {
return "喵喵喵!";
}
}


发生重写 Override - Animal 类 和 Cat 类中的 speak 方法的方法名和参数都一样


上面的 TypeScript 代码片段是一个基础的继承示例。Dog 和 Cat 类继承自 Animal,这意味着它们拥有 Animal 所有的属性和方法,我们不需要重新去定义它们,除非我们想覆盖原来的实现,就像上面的例子一样。继承允许我们将公共行为、状态(方法和属性)抽象到一个单独的地方,我们可以从那里(父类)提取。

const d1 = new Dog();
const c1 = new Cat();

console.log(d1.speak());
console.log(c1.speak());

​在线地址​

继承的另一大好处是用父类的类型声明的变量也可以兼容来自子类的对象,主要体现在 Java,TypeScript 等强类型语言。比如:

let animals:Animal[] = [new Cat(), new Dog()]
animals.forEach( a => console.log(a.speak()));

​在线地址​

紧接着之前定义的类,这个例子声明了一组 Animal 类型的元素,包含了一个 Cat 和 一个 Dog。这是可行的,因为两个对象有相同的父类。同样,我们可以在安全地调用 speak 方法。因为这个方法已经在父类中定义了,我们知道所有的对象都有它。


重载 Overload


工程意义: 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理


因为重载方法不是用来满足定义在父类(发生继承场景)的多态,所以重载的方法比较有扩展性。

class Overloads {
uniqueID: number | string | null;
constructor() {
this.uniqueID = null;
}
addNums(a: string, b: string);
addNums(a: number, b: number);
addNums(a: string, b: number);
addNums(a: number, b: string);
addNums(a: number|string, b: number| string): number {
if (typeof a === 'string') {
a = Number(a);
}
if (typeof b === 'string') {
b = Number(b);
}
return a + b;
}

setUniqueID(theID: string);
setUniqueID(ssNumber: number)
setUniqueID(value: number|string) {
if (typeof value === 'number') {
this.uniqueID = value
} else if (typeof value === 'string') {
const numberString = value + "";
this.uniqueID = value
}
}

}


发生重载 Overload - Overloads的同一个类中的多个 addNums 方法名相同,参数不同


执行测试

const o = new Overloads();
console.log('------- example1 ------')
console.log(o.addNums(1, 2));
console.log(o.addNums('3','5'));



console.log('------- example2 ------')
console.log(o.setUniqueID('1'));
console.log(o.uniqueID)

console.log(o.setUniqueID(2));
console.log(o.uniqueID)

​在线地址​

在 Java 中是这样子的

public class Overloads {

String uniqueID;

public int addNums(int a, int b) {
return a + b;
}

public double addNums(double a, double b) {
return a + b;
}

public void setUniqueID(String theID) {
uniqueID = theID
}

public void setUniqueID(int ssNumber) {
String numString = "" + ssNumber;
setUniqueID(numString)
}
}


重写与重载的区别


区别点



重载Overload方法



重写Overwrite方法



参数列表



必须修改



一定不能修改



返回类型



可以修改



一定不能修改



访问权限



可以修改



一定不能做更严格的限制

(可以降低限制,比如 protected 修改 public)



方法的重写(Overriding)和重载(Overloading)是面向对象中多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。

  • (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。

  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

更严厉的权限描述

type Rect = {
x: number,
y: number
}

class Animal {
protected flegCount:number;
protected isAlive:boolean;
protected color:string;
protected position: Rect;

constructor(){
this.flegCount = 4;
this.isAlive = true;
this.color = "white"

this.position = {
x: 0,
y: 0
}
}

public speak():string|null {
return null;
}
}

class Dog extends Animal {

speak():string|null {
return "旺旺旺!";
}
}


class PrivateDog extends Animal {
//Class 'PrivateDog' incorrectly extends base class 'Animal'.
//Property 'speak' is protected in type 'PrivateDog' but public in type 'Animal'.
protected speak():string|null {
return "严厉的旺旺旺!";
}
}




const d = new Dog();
console.log(d.speak())

​在线地址​

抽象类 Abstract Class


工程意义:因为抽象类不能实例化对象,所以必须要有子类来实现它之后才能使用。这样就可以把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护


abstract class Department {

constructor(public name: string) {
}

printName(): void {
console.log('Department name: ' + this.name);
}

abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {

constructor() {
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}

printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}

generateReports(): void {
console.log('Generating accounting reports...');
}
}

let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在

​在线地址​

什么时候用类或者接口

  1. 抽象类适合用来定义某个领域的固有属性,也就是本质,接口适合用来定义某个领域的扩展功能。

  2. 当需要为一些类提供公共的实现代码时,应优先考虑抽象类。因为抽象类中的非抽象方法可以被子类继承下来,使实现功能的代码更简单。

  3. 当注重代码的扩展性跟可维护性时,应当优先采用接口。

  1. ①接口与实现它的类之间可以不存在任何层次关系,接口可以实现毫不相关类的相同行为,比抽象类的使用更加方便灵活;

  2. ②接口只关心对象之间的交互的方法,而不关心对象所对应的具体类。接口是程序之间的一个协议,比抽象类的使用更安全、清晰。一般使用接口的情况更多。

案例1 接口:植物园内的水果,可以被拆下来,并且未来可以做成果汁

OOP 设计模式:接口与类的封装和重写(override)以及重载(overload)_前端


案例2 抽象类:狗狗可以有很多品种

OOP 设计模式:接口与类的封装和重写(override)以及重载(overload)_typescript_02



案例3 接口:交通工具

OOP 设计模式:接口与类的封装和重写(override)以及重载(overload)_抽象类_03


案例4 抽象类:企业雇佣的员工分类(小时工,周薪员工等员工)

OOP 设计模式:接口与类的封装和重写(override)以及重载(overload)_typescript_04


附录

  • 《Head First Java》 Head first java chapter 7 继承与多态_dengmu1918的博客

  • 接口与抽象类的区别 深入理解Java的接口和抽象类 - Matrix海子 

  • 何时使用接口(抽象类)? - dashuai的博客