设计模式有很多,这些设计模式的出现是由准则或者说有根据的,不是平白无故出现的,下面我们就来认识一下这些设计原则。
在此以前,我想说一下,看这篇文章的读者应该都是前端工程师或者使用JS编程语言的程序员,为了结合JS语言的特性(弱类型,无接口等)有一些原则只是简单的说一下,在JS中基本不会使用,只需要你了解。
如果想要全面学习设计原则以及23种设计模式,应该选择Java而不是JS。我们只针对JS语言和前端过程中可以使用到的一些设计原则。
1 单一职责原则(S):一个程序只做一件事,如果事情过于复杂,那么就应该拆开,使得每一部分保持独立。
2 开发封闭原则(0):对修改封闭,对扩展开发。在遇到新的需求增加时,应该扩展新代码而不是修改原来的代码。
3 里氏代换原则(L):父类出现的地方子类也可以,也就是子类可以替换父类而且使得程序可以正常运行。
4 接口隔离原则(I):接口的设计应该保持单一功能,使用多个单一功能的接口比使用一个总接口要好。
5 依赖倒转原则(d):抽象不应该依赖于细节(实现类),细节应该依赖于抽象。高层模板不应该依赖于低层模块,应该依赖于抽象。
6 迪米特原则原则(最少知道原则):一个类对另外一个类应该了解越少越好,高内聚,低耦合。
回归上面提到的,我们是前端工程师,这后面四种作为了解内容,但是前面俩种必须知道,这是我们的重点内容。
单一职责和开发封闭是我们应该需要掌握的内容,回想一下自己以前写的代码是否符合这俩个准则。
举一个简单的例子来熟悉一下这俩个准则。
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 1
2function loadImg(src) {
3 return new Promise((resolve, reject) => {
4 const img = document.createElement("img");
5 img.src = src;
6 img.onload = function () {
7 resolve(img);
8 }
9 img.onerror = function () {
10 reject("图片加载错误!")
11 }
12 })
13}
14
15let src = "http://img0.imgtn.bdimg.com/it/u=276772730,3789231562&fm=26&gp=0.jpg";
16const result = loadImg(src);
17result.then((img) => {
18 console.log(`${img.width}`)
19 return img;
20}).then((img) => {
21 console.log(`${img.height}`)
22 return img
23}).catch((err) => {
24 console.log(err);
25})
26
这是一个使用promise来加载图片的例子。
这个例子里面我使用了俩个then第一个then里面打印的是img.width 第二个then里面是打印img.height。虽然看起来是很傻的俩个操作,但是我想强调的是,每一个then就相当于一个程序段,里面只需要完成自己单一的任务,当增加其他功能时,我们不需要修改上面的俩个then,再添加一个then即可。
假如我们要进行的操作是否复杂,而且十分庞大,全部都放在一个程序段里面完成,那么在后期维护上面就十分的复杂了。上面的例子也许不是很恰当,但是能够表示出意思。
单一职责和开放封闭是我们学习的重点法则。
接下来 我们看俩道题目,来帮助大家理解面向对象设计原则。
大家觉得这题目如何,假如需要你设计你该如何设计了?思考一下,然后在继续看下面内容。
首先,我们可以知道任何车都有车牌号和名称,也就是说我们可以抽象一个车作为父类,包含车牌号和名称。因为价格,快车和专车不一样,我们可以把价格属性放到子类(快车和专车)中。
大家现在想一下,行程和是什么车有关系吗?快车有行程,专车也有行程,即行程应该和父类关联起来,而不是子类。最后一点,打车金额是行程的还是车的? 很明显应该是行程的。
画出UML图
写出JS代码
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
34
35
36 1class Car {
2 constructor(name, number) {
3 this.name = name;
4 this.number= number;
5 }
6}
7
8class KuaiChe extends Car {
9 constructor(name, number) {
10 super(name, number);
11 this.price = 1;
12 }
13}
14class ZhuanChe extends Car {
15 constructor(name, number) {
16 super(name, number);
17 this.price = 2;
18 }
19}
20class Trip {
21 constructor(c) {
22 this.car = c;
23 }
24 start() {
25 console.log(`${this.car.name}车为你服务,车牌号为${this.car.number}`)
26 }
27 end() {
28 console.log(`你需要支付${this.car.price * 5}元`)
29 }
30}
31
32const c = new KuaiChe("奔驰", 100);
33const t = new Trip(c);
34t.start();
35t.end();
36
下面题目会有难度
这道题目相较于上面一道题目难度提升很大。
从第一句话就应该了解到,我们需要建立停车场类(park) 层类(floor) 停车位类(place)
第二句话,我可以得到每个车位应该有俩个方法(in out)来表示车的驶入和离开,还应该在停车位类上面设置一个标志位isEmpty表示是否是空的。
第三句话:我们应该知道停车场里面有一个方法用于计算机每一层的空位,那么计算每一层的空位应该谁来做比较好?很明显应该是floor类来做。
第四句话:我们应该知道还需要一个摄像头类,当车辆进入后,用来记录车牌号和时间,记录的值应该保存在停车场里面。
第五句话:我们应该需要一个显示器类,当车辆出来时,显示车牌号和停车时长。
UML类图如下:
我们用代码实现一下
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 1class Car { //车类
2 constructor(number) {
3 this.number = number; //车牌号
4 }
5}
6
7class Camera {//照相机类
8 constructor() {};
9 shot(car) {
10 return {
11 number: car.number,
12 cTime: Date.now()
13 }
14 }
15}
16class Screen { //显示器
17 constructor() {};
18 show(obj) {
19 console.log(`车牌号${obj.number}准备离开,停车时间${Date.now()-obj.cTime}`)
20 }
21}
22
23class Place { // 车位
24 constructor() {
25 this.isEmpty = true;
26 }
27 in() { //有车进入
28 this.isEmpty = false;
29 }
30 out() { //有车出去
31 this.isEmpty = true;
32 }
33}
34
35class Floor { // 层类
36 constructor(index, places) {
37 this.index = index;
38 this.places = places;
39 }
40 calEmptyPlace() { //计算每一层空位
41 let cnt = 0;
42 this.places.forEach((item) => {
43 if (item.isEmpty) cnt++;
44 })
45 return cnt;
46 }
47}
48class Park { //停车场
49 constructor(floors) {
50 this.floors = floors;
51 this.camera = new Camera();
52 this.screen = new Screen();
53 this.car = {};
54 }
55 calEmpty() {
56 this.floors.forEach((item) => {
57 console.log(`第${item.index}层的空位是${item.calEmptyPlace()}个\n`)
58 })
59 }
60 in(car) {
61 this.calEmpty(); //显示有多少车辆
62
63 const info = this.camera.shot(car); //拍照
64 //停在那里 这个由车主自己决定
65 const index = 0; //假设第一层
66 const pot = parseInt((Math.random() * 100) % 100);
67 const place = this.floors[index].places[pot]; //取车位
68
69 place.in(); //进入
70
71 this.car[info.number] = {
72 info,
73 place
74 }//保存拍照信息
75 }
76 out(car) {
77 this.screen.show(this.car[car.number].info); //显示
78 this.car[car.number].place.out();
79 }
80}
81
82const floors = [];
83for (let i=0; i<3; i++) {
84 let places = [];
85 for (let j=1; j<=100; j++) {
86 places[j] = new Place();
87 }
88 floors[i] = new Floor(i+1, places);
89}
90
91const p = new Park(floors);
92
93const car1 = new Car(1);
94const car2 = new Car(2);
95const car3 = new Car(3);
96
97console.log("第一辆车来了")
98p.in(car1);
99console.log("第二辆车来了")
100p.in(car2);
101console.log("第一辆车走了")
102p.out(car1);
103console.log("第二辆车走了")
104p.out(car2);
105console.log("第三辆车来了")
106p.in(car3);
107console.log("第三辆车走了")
108p.out(car3);
109
110
看一下效果如何
预期效果一样。