设计模式:类和对象及面向对象

面向对象

面向对象程序设计(Object-Oriented Programming, OOP)是一种范式,基本理念是将数据块与数据相关的行为封装成特殊的、名为对象的实体,同时对象实体的生成工作则是由程序员给出的一系列“蓝图”,这些蓝图就是“类”。

类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础。类是一种用户定义的引用数据类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。

这里有一个 Cat 类,我们用 UML 类图来表示一下。

这是一个 Cat, 包含了成员变量(状态)、方法(行为),+ 代表共用方法,同样可以用 - 代表私有方法。类 Cat 就是对猫这一动物的抽象 用代码来描述就是:

class Cat {
    constructor(name, gender, age, weight, color) {
        this.name = name
        this.gender = gender
        this.weight = weight
        // ...
    }
    breath() {}
    eat(food: Food){}
    run(desitination: number){}
    // ...
}

对象

如果说类是定义对象结构的蓝图,那么对象就是类的具体实例。

比如,你拥有一只叫做 penpen 的猫,也可以拥有一只叫做 lulu的猫:

这两只猫是不同的猫,区别在于拥有不同的属性。
用代码描述就是:

const penpen = new Cat("penpen", "male", 7 , /*省略...*/)
const lulu = new Cat("lulu", "female", 5, /*省略...*/)

INFO

JavaScript 是一门多范式的编程语言,ES6 之后又提供了 class 语法糖。这么看来 JavaScript 也是一门面向对象的编程语言

面向对象设计基础

面向对象程序设计的四个基本概念使其区别于其他的编程范式。

抽象

抽象是一种反映真实世界对象或现象中特定内容的模型, 它能高精度地反映所有与特定内容相关的详细信息, 同时忽略其他内容。

例如,飞机模拟器和航班预订中都会包含一个 Airplane 类,但两者对飞机的具体抽象是不同的,前者包括了飞机飞行相关的信息,后者只需要关心座位图,以及座位是否可被预订即可:

// 飞行模拟器
class Airplane {
	speed: number
	altitude: number
	rollAngle: number
	pitchAngle: number
	yawAngle: number
	fly() {}
}
// 航班预订
class Airplane {
	seats: number
	reserveSeat(n) {}
}

封装

封装是指一个对象对其他对象隐藏其部分状态和行为,而仅向程序其他部分暴露有限的接口的能力。

比如想要启动一辆车,我们只需要转动钥匙即可启动,无需打开引擎,手动界限,转动曲轴等一些列操作。这些操作细节都被隐藏在了汽车内部,你所看到的只有一些简单的接口:开关,方向盘,刹车等。

大部分编程语言的接口和抽象类都基于抽象和封装的概念。 比如通过接口(interface) 去规范及定义对象之间的交互协议:

// 多个类实现同一个接口
interface Transport {
	move(origin: number, destination: number, passengers: number): void
}

class Airplane implements Transport {
	move(origin: number, destination: number, passengers: number): void {}
}

class Bus implements Transport {
	move(origin: number, destination: number, passengers: number): void {}
}

class Taxi implements Transport {
	move(origin: number, destination: number, passengers: number): void {}
}

继承

继承是指根据已有类创建新类的能力。继承最主要的好处是复用代码。

继承最大的好处就是避免重复编写大量相同的代码。只需要扩展已有类,并把新的功能放到子类中。使用继承后,子类将拥有和父类相同的接口。 在大多数编程语言中,子类仅能继承自一个父类,但是可以实现多个接口:

interface Legged {
	run(destination: number):void
}
interface Breather {
	breathe(): void
}
class Animal implements Legged, Breather {
  name: string
  age: number
  constructor(name:string, age: number) {
    this.name = name
    this.age =age
  }
  breathe(): void {
    throw new Error('Method not implemented.')
  }
  run(destination: number): void {
    throw new Error('Method not implemented.')
  }
}

class Cat extends Animal {
  meow() {
    console.log('meow meow...')
  }
}

const cat = new Cat('no.88', 3)
console.log(cat.name)
console.log(cat.age)
cat.breathe()
cat.meow()

多态

多态是指程序能够检测对象所属的实际类,并在当前上下文不知道其真实类型情况下调用其实现的能力。

多态其实就是我们平时开发中经常见到的 duck typing 的概念:

class Animal {
    makeSound() {}
}
class Cat extends Animal {
    makeSound() {
        console.log('meow meow..')
    }
}
class Dog extends Animal {
    makeSound() {
        console.log('bark bark...')
    }
}
const animals = [new Cat(), new Dog()]

animals.forEach(animal => animal.makeSound())
// meow meow...
// bark bark...

对象之间的其他关系

除了上面有提到过的继承、实现之外,对象之间还存在着一下几种关系

依赖

修改A类的定义造成B类的变化,这两个类之间就存在依赖关系。

class Course {
	learnMathmatic() {
		console.log('learn mathmatic')
	}
}

class Studen {
	getKnownledge() {
		new Course().learnMathmatic()
	}
}

如果一不小心修改了 Course 中的 learnMathmatic 方法(实现逻辑/方法签名)的话,学生获取的可能就不是数学知识了,这就是依赖关系。

关联

关联是一个对象使用另一个对象或与另一个对象进行交互的关系

class Student {
	remember(knownledge: any) {}
}
class Course {
	getKnownledge(){}
}
class Professor {
	student!: Student
	teach(c: Course) {
		this.student.remember(c.getKnownledge())
	}
}

上面的例子中,Professor 教授与 Student 类就产生了关联关系,但是 Student 和 Course 是依赖关系。关联是一种特殊的依赖。从代码形式上来看,依赖更像是构造函数的调用,A 类的构造函数依赖 B 类构造函数的实例。而关联更像是 A 的的成员变量是 B 类的实例。

聚合

聚合是一种特殊类型的关联,用于表示多个对象之间“一对多”、“多对多”或“整体对部分”的关系。

聚合更是一种强耦合的关联关系。关联和聚合的代码表现形式比较类似,仅仅是语义上有些差别:关联关系中类都是相互独立的,但是聚合关系中更像是包容关系,即 A 类 包容 B 类:

class KeyBoard {}
class Mouse {}
class Computer {
	keyboard!: KeyBoard
	mouse!: Mouse
}

Computer 类中包含了 KeyBoard 类和 Mouse 类,即整体与个体的区别,其中 Computer 和 KeyBoard 是可以分离的,各自都具有独立的生命周期,KeyBoard 可以分属于多个 Computer 类,也可以被多个对象共享。

组合

组合是一种特殊类型的聚合,其中一个对象由多个其他对象的实例构成。

组合是比聚合耦合度更强的一种关联关系。组合和聚合在代码层面上也没有什么区别,更多是在语义话上的区别。组合于聚合同样都是 整体-部分的关系,但聚合和组合的唯一区别就是,对于组合来说个体脱离了整体是无法单独存在的。

class Brain{}
class Person {
	brain!: Brain
}

其中 Brain(大脑)被包含于 Person 中,但是两者有共同的生命周期,Person 死掉的话,大脑同样会死掉,且大脑无法脱离 Person 单独生存。

总结

简单了解了一下类与对象之间的关系后,就可以很方便的进行设计模式的学习了。