面向对象编程的元素
面向对象编程(Object-Oriented Programming,OOP):OOP 是基于对象的概念的一种编程范式,对象可以包含数据和代码。数据是对象的状态,代码是一个或多个方法,也叫作“消息”。在面向对象系统中,通过使用其他对象的方法,对象之间可以“对话”或者发送消息。
接口或契约:接口(或契约)描述了实现该接口的任何对象都理解的一组消息。消息是方法,包括名称、实参和返回类型。接口没有任何状态。与现实世界的契约(它们是书面协议)一样,接口也相当于书面协议,规定了实现者将提供什么。
1
2
3
4
5
6
7
8
9interface ILogger {
log(line: string): void;
}
class SLogger implements ILogger {
log(line: string): void {
console.log(line);
}
}继承和“是一个”关系:继承会在子类型与父类型之间建立“是一个”关系。如果基类是
Shape,派生类是Circle,关系就是“Circle是一个Shape”。这是继承在语义上的含义,可用来判断是否应该在两个类型之间应用继承。抽象类和“普通”类,或者说具体类的唯一区别在于,我们不能直接创建抽象类的实例。
通常,让子类是具体类,让层次结构上方的父类是抽象类,这是一个好主意。这种技术使得跟踪信息和避免意外行为变得更加简单。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18interface Radius {
radius(): number;
}
abstract class Shape implements Radius {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
abstract radius(): number;
}
class Circles extends Shape {
radius(): number {
return this.x * this.y;
}
}组合和“有一个”关系:组合在容器类型和被包含类型之间建立了一种“有一个”关系。如果类型是
Circle,被包含的类是Point,那么它们的关系是“Circle有一个Point”(该Point定义了Circle的圆心)。这是组合在语义上的意义,可用于判断是否应该在两个类型之间使用组合。当我们把一个值类型的实例赋值给一个变量,或者将其作为实参传递给一个函数时,在内存中会复制其内容,实际上创建了另外一个实例。另一方面,当我们赋值一个引用类型的实例时,并不会复制其完整的状态,而只是复制其引用。原变量和新变量将指向相同的对象,并能够修改其状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Shape {
id: string;
constructor(id: string) {
this.id = id;
}
}
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Circle extends Shape {
center: Point;
radius: number;
constructor(id: string, center: Point, radius: number) {
super(id);
this.radius = radius;
this.center = center;
}
}混入和包含关系:混入在一个类型与其混入类型之间建立了“包含”关系。如果类是 Cat,混入类是
HunterBehavior,那么二者的关系是“Cat包含HunterBehavior”。这是混入在语义上的含义,与继承的“是一个”关系不同(如图 8.8 所示)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function extend<First, Second>(first: First, second: Second): First & Second {
const result: unknown = {};
for (const prop in first) {
if (first.hasOwnProperty(prop)) {
(<First>result)[prop] = first[prop];
}
}
for (const prop in second) {
if (second.hasOwnProperty(prop)) {
(<Second>result)[prop] = second[prop];
}
}
return result;
}
class MeowIngPet implements Pet {
meow(): void {}
}
class HuntBehavior {
track(): void {}
stalk(): void {}
pounce(): void {}
}
type Cat = MeowIngPet & HuntBehavior;
const fluffy: Cat = extend(new MeowIngPet(), new HuntBehavior());