此法则适合所有语言,咱们以 JavaScript 和 Java 两个角度分析一下这个东东。
有这样的一个页面, js 、 css 代码都写在 html 页面中。
例如: gnj.html
版本
1 <! DOCTYPE html >
2 < html lang ="en" >
3 < head >
4 < meta charset ="UTF-8" >
5 < meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
6 < meta http-equiv ="X-UA-Compatible" content ="ie=edge" >
7 < title > Document </ title >
8 </ head >
9 < body >
);
12 </ script > 13 < style >
14 h1 {
15 background-color : blueviolet ;
16 }
17 </ style >
18 < h1 > 标题 </ h1 >
19 </ body >
20 </ html >
21
这个页面承载了多个功能:定义 html 需要的 javascript 脚本,定义 html 需要的 css 样式,还有定义页面需要显示的元素。
这样的代码编写方式就像下面两个拼拼凑凑的动物:
龙:
角似鹿、头似牛、眼似虾、嘴似驴、腹似蛇、鳞似鱼、足似凤、须似人、耳似象
麋鹿:
角似鹿非鹿、鼻子似牛非牛、身体似驴非驴、尾巴似马非马
问题:代码内部比较臃肿,复用度很低。 js 不能被多个 html 复用, css 也不能被多个 html 复用。耦合性较高。
优化后的代码,如下:
gnj.js
1 document.write("高内聚低耦合demo");
h1.css
1 h1 {
2 background-color: blueviolet;
3 }
4
gnj_v2.html
1 <! DOCTYPE html >
2 < html lang ="en" >
3 < head >
4 < meta charset ="UTF-8" >
5 < meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
6 < meta http-equiv ="X-UA-Compatible" content ="ie=edge" >
7 < title > Document </ title >
8 < script src ="./gnj.js" ></ script >
9 < link rel ="stylesheet" type ="text/css" href ="h1.css" />
10 </ head >
11 < body >
12
13 < h1 > 标题 </ h1 >
14 </ body >
15 </ html >
16
高内聚:模块内的事。模块内,联系越紧密,内聚性越高。
低耦合 :模块间的事,相关的操作,不再直接相互依赖调用
再来看一个 java 的中午吃饭过程的例子:
版本
1 package com.gavin.controller;
2
3 import org.springframework.web.bind.annotation.GetMapping;
4 import org.springframework.web.bind.annotation.RestController;
5
* Created by gavinmiao on 2019/8/30
8 */9 @RestController
10 public class DemoController {
11 @GetMapping("/lunch")
12 public String haveLunch(){
13 StringBuilder builder = new StringBuilder();
14 builder.append("<html>");
15 // 排队
16 builder.append(String.format("%s <br/>","--------------------------------------------"));
17 builder.append(String.format("%s <br/>","(^o^)开始排队(^o^)"));
18 builder.append(String.format("%s <br/>","1只羊"));
19 builder.append(String.format("%s <br/>","2只羊"));
20 builder.append(String.format("%s <br/>","3只羊"));
21 builder.append(String.format("%s <br/>","4只羊"));
22 builder.append(String.format("%s <br/>","5只羊"));
23 builder.append(String.format("%s <br/>","6只羊"));
24 builder.append(String.format("%s <br/>","7只羊"));
25 builder.append(String.format("%s <br/>","8只羊"));
26 builder.append(String.format("%s <br/>","9只羊"));
27 builder.append(String.format("%s <br/>","(-o-)结束排队(-o-)"));
28 // 点菜
29 builder.append(String.format("%s <br/>","--------------------------------------------"));
30 builder.append(String.format("%s <br/>","(^o^)开始点菜(^o^)"));
31 builder.append(String.format("%s <br/>","蒸羊羔"));
32 builder.append(String.format("%s <br/>","蒸熊掌"));
33 builder.append(String.format("%s <br/>","蒸鹿尾儿"));
34 builder.append(String.format("%s <br/>","烧花鸭"));
35 builder.append(String.format("%s <br/>","烧雏鸡"));
36 builder.append(String.format("%s <br/>","烧子鹅"));
37 builder.append(String.format("%s <br/>","卤猪"));
38 builder.append(String.format("%s <br/>","卤鸭"));
39 builder.append(String.format("%s <br/>","酱鸡"));
40 builder.append(String.format("%s <br/>","腊肉"));
41 builder.append(String.format("%s <br/>","松花"));
42 builder.append(String.format("%s <br/>","小肚儿"));
43 builder.append(String.format("%s <br/>","(-o-)结束点菜(-o-)"));
44
45 // 取餐
46 builder.append(String.format("%s <br/>","--------------------------------------------"));
47 builder.append(String.format("%s <br/>","(^o^)开始取餐(^o^)"));
48 builder.append(String.format("%s <br/>","一盘蒸羊羔"));
49 builder.append(String.format("%s <br/>","一盘蒸熊掌"));
50 builder.append(String.format("%s <br/>","一盘蒸鹿尾儿"));
51 builder.append(String.format("%s <br/>","一盘烧花鸭"));
52 builder.append(String.format("%s <br/>","一盘烧雏鸡"));
53 builder.append(String.format("%s <br/>","一盘烧子鹅"));
54 builder.append(String.format("%s <br/>","一盘卤猪"));
55 builder.append(String.format("%s <br/>","一盘卤鸭"));
56 builder.append(String.format("%s <br/>","一盘酱鸡"));
57 builder.append(String.format("%s <br/>","一盘腊肉"));
58 builder.append(String.format("%s <br/>","一盘松花"));
59 builder.append(String.format("%s <br/>","一盘小肚儿"));
60 builder.append(String.format("%s <br/>","(-o-)结束取餐(-o-)"));
61
62 // 用餐
63 builder.append(String.format("%s <br/>","--------------------------------------------"));
64 builder.append(String.format("%s <br/>","(^o^)开始用餐(^o^)"));
65 builder.append(String.format("%s <br/>","蒸羊羔好吃"));
66 builder.append(String.format("%s <br/>","蒸熊掌好吃"));
67 builder.append(String.format("%s <br/>","蒸鹿尾儿好吃"));
68 builder.append(String.format("%s <br/>","烧花鸭好吃"));
69 builder.append(String.format("%s <br/>","烧雏鸡好吃"));
70 builder.append(String.format("%s <br/>","烧子鹅好吃"));
71 builder.append(String.format("%s <br/>","卤猪好吃"));
72 builder.append(String.format("%s <br/>","卤鸭好吃"));
73 builder.append(String.format("%s <br/>","酱鸡好吃"));
74 builder.append(String.format("%s <br/>","腊肉好吃"));
75 builder.append(String.format("%s <br/>","松花好吃"));
76 builder.append(String.format("%s <br/>","小肚儿好吃"));
77 builder.append(String.format("%s <br/>","(-o-)结束用餐(-o-)"));
78 builder.append(String.format("%s <br/>","--------------------------------------------"));
79 builder.append("</html>");
80 return builder.toString();
81 }
82
83 }
84
代码运行如下:
仔细阅读以上代码,发现有很多重复的地方,比如分割线和添加字符串操作。基于这两个重复的地方,咱们可以优化一下。单独提供两个方法,一个获取分割线,另外一个处理字符串拼接。
V1版本
1 package com.gavin.controller;
2
3 import org.springframework.web.bind.annotation.GetMapping;
4 import org.springframework.web.bind.annotation.RestController;
5
* Created by gavinmiao on 2019/8/30
8 */9 @RestController
10 public class DemoV1Controller {
11 @GetMapping("/v1/lunch")
12 public String haveLunch(){
13 StringBuilder builder = new StringBuilder();
14 builder.append("<html>");
15 // 排队
16 appendStr(builder,getSeparator());
17 appendStr(builder,"(^o^)开始排队(^o^)");
18 appendStr(builder,"1只羊");
19 appendStr(builder,"2只羊");
20 appendStr(builder,"3只羊");
21 appendStr(builder,"4只羊");
22 appendStr(builder,"5只羊");
23 appendStr(builder,"6只羊");
24 appendStr(builder,"7只羊");
25 appendStr(builder,"8只羊");
26 appendStr(builder,"9只羊");
27 appendStr(builder,"(-o-)结束排队(-o-)");
28 // 点菜
29 appendStr(builder,getSeparator());
30 appendStr(builder,"(^o^)开始点菜(^o^)");
31 appendStr(builder,"蒸羊羔");
32 appendStr(builder,"蒸熊掌");
33 appendStr(builder,"蒸鹿尾儿");
34 appendStr(builder,"烧花鸭");
35 appendStr(builder,"烧雏鸡");
36 appendStr(builder,"烧子鹅");
37 appendStr(builder,"卤猪");
38 appendStr(builder,"卤鸭");
39 appendStr(builder,"酱鸡");
40 appendStr(builder,"腊肉");
41 appendStr(builder,"松花");
42 appendStr(builder,"小肚儿");
43 appendStr(builder,"(-o-)结束点菜(-o-)");
44
45 // 取餐
46 appendStr(builder,getSeparator());
47 appendStr(builder,"(^o^)开始取餐(^o^)");
48 appendStr(builder,"一盘蒸羊羔");
49 appendStr(builder,"一盘蒸熊掌");
50 appendStr(builder,"一盘蒸鹿尾儿");
51 appendStr(builder,"一盘烧花鸭");
52 appendStr(builder,"一盘烧雏鸡");
53 appendStr(builder,"一盘烧子鹅");
54 appendStr(builder,"一盘卤猪");
55 appendStr(builder,"一盘卤鸭");
56 appendStr(builder,"一盘酱鸡");
57 appendStr(builder,"一盘腊肉");
58 appendStr(builder,"一盘松花");
59 appendStr(builder,"一盘小肚儿");
60 appendStr(builder,"(-o-)结束取餐(-o-)");
61
62 // 用餐
63 appendStr(builder,getSeparator());
64 appendStr(builder,"(^o^)开始用餐(^o^)");
65 appendStr(builder,"蒸羊羔好吃");
66 appendStr(builder,"蒸熊掌好吃");
67 appendStr(builder,"蒸鹿尾儿好吃");
68 appendStr(builder,"烧花鸭好吃");
69 appendStr(builder,"烧雏鸡好吃");
70 appendStr(builder,"烧子鹅好吃");
71 appendStr(builder,"卤猪好吃");
72 appendStr(builder,"卤鸭好吃");
73 appendStr(builder,"酱鸡好吃");
74 appendStr(builder,"腊肉好吃");
75 appendStr(builder,"松花好吃");
76 appendStr(builder,"小肚儿好吃");
77 appendStr(builder,"(-o-)结束用餐(-o-)");
78 appendStr(builder,getSeparator());
79 builder.append("</html>");
80 return builder.toString();
81 }
82
83 private String getSeparator(){
84 return "--------------------------------------------";
85 }
86
87 private void appendStr(StringBuilder builder,String 啊我额){
88 builder.append(String.format("%s <br/>",啊我额));
89 }
90 }
91
代码运行如下:
刚刚单独处理了一下分割线,那一般分割线因人而异,爱好不同,分割线样式也不同。像这种分割线有很多种样式,怎么办呢?有的同学会想到,编写接口,提供多个实现类。对,大致思路是这样,还有一个细节同学们没想到,就是最终需要做一个决策,到底使用哪种分割线样式。这个决策,我们让 controller 自己来确定。
V2 版本
1 package com.gavin.controller;
2
3 import com.gavin.common.SeparatorContext;
4 import com.gavin.service.GenSeparator;
5 import com.gavin.service.impl.BoLangXianSeparator;
6 import org.springframework.beans.factory.annotation.Autowired;
7 import org.springframework.web.bind.annotation.GetMapping;
8 import org.springframework.web.bind.annotation.RestController;
9
10 import javax.annotation.Resource;
11
* Created by gavinmiao on 2019/8/30
14 */15 @RestController
16 public class DemoV2Controller {
17 @Autowired
18 private SeparatorContext separatorContext;
19 @Resource
20 private GenSeparator boLangXianSeparator;
21 @Resource
22 private GenSeparator greaterThanSeparator;
23 @Resource
24 private GenSeparator hengGangSeparator;
25
26 @GetMapping("/v2/lunch")
27 public String haveLunch(){
28 StringBuilder builder = new StringBuilder();
29 builder.append("<html>");
30 // 排队
31 appendStr(builder,getSeparator());
32 appendStr(builder,"(^o^)开始排队(^o^)");
33 appendStr(builder,"1只羊");
34 appendStr(builder,"2只羊");
35 appendStr(builder,"3只羊");
36 appendStr(builder,"4只羊");
37 appendStr(builder,"5只羊");
38 appendStr(builder,"6只羊");
39 appendStr(builder,"7只羊");
40 appendStr(builder,"8只羊");
41 appendStr(builder,"9只羊");
42 appendStr(builder,"(-o-)结束排队(-o-)");
43 // 点菜
44 appendStr(builder,getSeparator());
45 appendStr(builder,"(^o^)开始点菜(^o^)");
46 appendStr(builder,"蒸羊羔");
47 appendStr(builder,"蒸熊掌");
48 appendStr(builder,"蒸鹿尾儿");
49 appendStr(builder,"烧花鸭");
50 appendStr(builder,"烧雏鸡");
51 appendStr(builder,"烧子鹅");
52 appendStr(builder,"卤猪");
53 appendStr(builder,"卤鸭");
54 appendStr(builder,"酱鸡");
55 appendStr(builder,"腊肉");
56 appendStr(builder,"松花");
57 appendStr(builder,"小肚儿");
58 appendStr(builder,"(-o-)结束点菜(-o-)");
59
60 // 取餐
61 appendStr(builder,getSeparator());
62 appendStr(builder,"(^o^)开始取餐(^o^)");
63 appendStr(builder,"一盘蒸羊羔");
64 appendStr(builder,"一盘蒸熊掌");
65 appendStr(builder,"一盘蒸鹿尾儿");
66 appendStr(builder,"一盘烧花鸭");
67 appendStr(builder,"一盘烧雏鸡");
68 appendStr(builder,"一盘烧子鹅");
69 appendStr(builder,"一盘卤猪");
70 appendStr(builder,"一盘卤鸭");
71 appendStr(builder,"一盘酱鸡");
72 appendStr(builder,"一盘腊肉");
73 appendStr(builder,"一盘松花");
74 appendStr(builder,"一盘小肚儿");
75 appendStr(builder,"(-o-)结束取餐(-o-)");
76
77 // 用餐
78 appendStr(builder,getSeparator());
79 appendStr(builder,"(^o^)开始用餐(^o^)");
80 appendStr(builder,"蒸羊羔好吃");
81 appendStr(builder,"蒸熊掌好吃");
82 appendStr(builder,"蒸鹿尾儿好吃");
83 appendStr(builder,"烧花鸭好吃");
84 appendStr(builder,"烧雏鸡好吃");
85 appendStr(builder,"烧子鹅好吃");
86 appendStr(builder,"卤猪好吃");
87 appendStr(builder,"卤鸭好吃");
88 appendStr(builder,"酱鸡好吃");
89 appendStr(builder,"腊肉好吃");
90 appendStr(builder,"松花好吃");
91 appendStr(builder,"小肚儿好吃");
92 appendStr(builder,"(-o-)结束用餐(-o-)");
93 appendStr(builder,getSeparator());
94 builder.append("</html>");
95 return builder.toString();
96 }
97
98 private String getSeparator(){
return separatorContext.getSeparator(boLangXianSeparator);
100 // return separatorContext.getSeparator(hengGangSeparator);102 }
103
104 private void appendStr(StringBuilder builder,String 啊我额){
105 builder.append(String.format("%s <br/>",啊我额));
106 }
107 }
108
代码运行如下:
前 3 个版本我们只是处理了一下整个吃饭过程中的小细节。
真正的吃饭过程的代码还是很长的,得翻好多屏,并且排队、点菜、取餐、用餐, 4 块逻辑,顺序执行,单独某一块比较独立。另一个是,没使用上 MVC 分层思想,应该将业务代码放到业务层中。这样 controller 中的代码就很少了。业务层,我们也可以按业务功能细分一下,针对 controller 中出现的 4 块逻辑,各自创建一个 Service 类。这样就完美的解决了 MVC 问题与代码长的问题了。
最后一个问题,字符串处理属于公共逻辑,可以把它抽取到一个 StringUtil 的公共类中,供 Controller 和 Service 调用。
1 package com.gavin.controller;
2
3 import com.gavin.common.SeparatorContext;
4 import com.gavin.common.StringUtil;
5 import com.gavin.service.*;
6 import org.springframework.beans.factory.annotation.Autowired;
7 import org.springframework.web.bind.annotation.GetMapping;
8 import org.springframework.web.bind.annotation.RestController;
9
10 import javax.annotation.Resource;
11
* Created by gavinmiao on 2019/8/30
14 */15 @RestController
16 public class DemoV3Controller {
17 @Autowired
18 private SeparatorContext separatorContext;
19 @Resource
20 private GenSeparator boLangXianSeparator;
21 @Resource
22 private GenSeparator greaterThanSeparator;
23 @Resource
24 private GenSeparator hengGangSeparator;
25
26 @Autowired
27 private OrderService orderService;
28 @Autowired
29 private QueueService queueService;
30 @Autowired
31 private TakeFoodService takeFoodService;
32 @Autowired
33 private HaveDinnerService haveDinnerService;
34
35
36 @GetMapping("/v3/lunch")
37 public String haveLunch(){
38 StringBuilder builder = new StringBuilder();
39 builder.append("<html>");
40 StringUtil.appendStr(builder,getSeparator());
41 StringUtil.appendStr(builder,queueService.execute());
42 StringUtil.appendStr(builder,getSeparator());
43 StringUtil.appendStr(builder,orderService.execute());
44 StringUtil.appendStr(builder,getSeparator());
45 StringUtil.appendStr(builder,takeFoodService.execute());
46 StringUtil.appendStr(builder,getSeparator());
47 StringUtil.appendStr(builder,haveDinnerService.execute());
48 StringUtil.appendStr(builder,getSeparator());
49 builder.append("</html>");
50 return builder.toString();
51 }
52
53 private String getSeparator(){
return separatorContext.getSeparator(boLangXianSeparator);
55 // return separatorContext.getSeparator(hengGangSeparator);57 }
58
59
60 }
61
代码运行如下:
从这 4 个版本中可以感受到,出现拼拼凑凑的感觉时,那么你的代码就是内聚性比较低的表现了。如果代码总要变来变去,其实是耦合高的表现。
最后,想要提高内聚性,可以通过降低耦合度来达到目的。在这儿,我个人提倡同学们编写高内聚、低耦合的代码。
【原创文章,转载请注明出处! GavinMiao】Gavin