在开始面向对象设计模式之前,我们必须了解一下什么是面向对象,什么是面向对象三要素。
面向对象:面向对象编程(Object Oriented Programming),所以也叫做OOP,这与我们早期的面向过程很不一样。早期计算机处理的问题都不是很复杂,所以一个算法,一个数据结构就能够很好的解决当时的问题。但是随着计算机技术的发展,要处理的计算机问题越来越复杂。为了更好的解决这样的问题,就出现了一切皆对象的面向对象编程,把计算机中的东西比喻成现实生活中的一样事物,一个对象。那现实生活中的对象都会有属性和行为,这就对应着计算机中的属性和方法(函数)。简答的说,就是一些属性和方法的集合,可以通过new 方法来生成对象实例。
下面我们介绍面向对象的三要素,并且结合JS(有可能是TS)来解释三种特性。
1:继承
所谓继承就是在无需修改原有类的情况下,对该类进行属性和功能上面的扩展。通过继承创建的类称为子类或者派生类,被继承的类被称为父类或者超类。
JS实现继承的方式有很多,比如原型继承,组合继承,寄生组合等(这不是重点),我们这里直接使用es6的语法糖extends.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 1class Person {
2 constructor(name, age) {
3 this.name = name;
4 this.age = age;
5 }
6 speak() {
7 console.log(`I am ${this.name}, ${this.age} years old.`)
8 }
9}
10
11
12class Student extends Person {
13 constructor(name, age, grade) {
14 super(name, age);
15 this.grade = grade;
16 }
17 doHomework() {
18 console.log(`I am a student and need to do homework.`)
19 }
20}
21
22const xiaozhang = new Student("xiaozhang", 21, "A-3");
23
24xiaozhang.speak();
25xiaozhang.doHomework();
26
这里我们先写了一个父类(人类,里面有一些基本属性,姓名和年龄,一个基本方法speak),还写了一个子类(继承于人类)里面额外有一个属性grade和一个方法doHomework()。我们调用speak和grade方法,都成功的打印了。
为什么可以使用name,age speak了?这都需要归功于继承这个功能,student在不需要额外扩展下就继承了父类的方法。
我们可以通过继承把公共的方法抽离出来,需要使用的类只需要继承它,那么就减少了代码的冗余和复杂度,提高了利用率。
2:封装
封装:就是把事物(属性和操作属性的方法)封装分成类,并且根据需要可以规定那些类可以访问那些属性或者方法。简答的说一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问,某个是可以给外界访问的,是共用的,而有一些只能给子类使用。
这里涉及的主要是三个修饰符,private(私有的),public(共有的),protected(受保护的)
因为原生JS没有提供这样类似的修饰符,所以我们就使用typescript来测试一下
http://www.typescriptlang.org/play/ (测试ts的位置)
我们看一下如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 1 class Person {
2 name //共用
3 age
4 protected weight
5 constructor(name, age) {
6 this.name = name;
7 this.age = age;
8 this.weight = 120;
9 }
10 speak() {
11 console.log(`I am ${this.name}, ${this.age} years old.`)
12 }
13}
14
15
16class Student extends Person {
17 private grade
18 constructor(name, age, grade) {
19 super(name, age);
20 this.grade = grade;
21 }
22 doHomework() {
23 console.log(`I am a student and need to do homework.`)
24 }
25 getWeight() {
26 console.log(`${this.weight}`)
27 }
28}
29
30const xiaozhang = new Student("xiaozhang", 21, "A-3");
31xiaozhang.getWeight();
32console.log(xiaozhang.grade);
33
我们定义了俩个public属性name age 一个protected weight 在子类Student中 我们定义了一个私有属性privategrade,然后生成了一个实例xiaozhang 访问getWeight是可以正常访问,但是访问grade时就出现错误了,错误也很明显了。
我们在外面访问weight了?同样出错。这很正常因为修饰符的原因。
使用这些修饰符,我们可以减少耦合,因为我们可以控制暴露率,一些不想暴露的就不暴露(这些在js一般是规定_开头的)
而且也利于管理。
3:多态
通俗易懂的讲,就是同一操作当操作对象不一样时,那么执行的操作也就不同。简答的说就是把做什么和谁来做,怎么做解耦。
看如下例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1class Person {
2 constructor(name, age) {
3 this.name = name;
4 this.age = age;
5 }
6 speak() {
7 console.log(`I am ${this.name}, ${this.age} years old.`)
8 }
9}
10
11
12class Student extends Person {
13 constructor(name, age, grade) {
14 super(name, age);
15 this.grade = grade;
16 }
17 speak() {
18 console.log(`I am a student.`)
19 }
20}
21
22const xiaozhang = new Student("xiaozhang", 21, "A-3");
23const p = new Person("p", 26);
24xiaozhang.speak();
25p.speak();
26
27
我们生成了俩个实例,分别调用speak方法,就分别调用了自己的speak方法。
多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。但是JS语言比较特殊,并不存在方法重载,并且在继承方面也与传统的接口/类不同,所以在JavaScript中大多是通过子类重写父类方法的方式实现多态,就像上面这段代码一样。
比如我还可以换一种写法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1class Person {
2 constructor(name, age) {
3 this.name = name;
4 this.age = age;
5 }
6}
7class Student extends Person {
8 constructor(name, age, grade) {
9 super(name, age);
10 this.grade = grade;
11 }
12}
13
14function speak(object) {
15 if (object instanceof Student) {
16 console.log(`Student`)
17 } else if (object instanceof Person) {
18 console.log(`Person`)
19 }
20}
21
22const xiaozhang = new Student("xiaozhang", 21, "A-3");
23const p = new Person("p", 26);
24speak(xiaozhang);
25speak(p);
26
27
通过if else判断来进行(但是我这里必须要强调一点 ,speak里面通过if else来进行 ,这是一个很low的代码,学了后面的设计模式就明白了,因为这样很不利于扩展,不满足面向对象设计原则)
上面的面向对象的三要素是很基础的内容,也是学习设计模式的基础知识。
最后我们谈一下为什么要使用面向对象。
我们知道一般程序执行是三种,判断,顺序,循环,非常简单,我们平时写的大部分代码都逃不开这三种(有一种goto但是不推荐使用)。这三种执行方式满足了我平时开发的所有需求,以至于不需要第四种,我可以称着三种方式结构化。
那么我们使用面向对象的原因是想实现数据结构化。
你假象一下,如果我们没有使用面向对象,那么假设有一个人,那么他可以有姓名,性别,年龄,身份证号,男女朋友,同学,朋友,父母,亲戚,老师,同事,爱好等 还有一些行为例如吃饭 睡觉 工作 学习 看电影 听音乐等。假如我们没有实现数据结构化,这些数据随便存放,那么一个人可能你还觉得还ok,假设有1千万个人了,那么这些数据处理起来,就可麻烦大了。
使用面向对象,我们将这些属性和对象封装起来实现数据结构化,那么处理起来就简单多了。
编程我们追求的就是简单和抽象。
简单的前提是抽象,抽象是为了简单。