此法則適合所有語言,咱們以JavaScript和Java兩個角度分析一下這個東東。
一、javascript
有這樣的一個頁面,js、css代碼都寫在html頁面中。
例如:gnj.html
v1版本
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>
10 <script>
11 document.write("高內聚低耦合demo");
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復用。耦合性較高。
優化后的代碼,如下:v2版本
gnj.js1 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
再來看一個java的中午吃飯過程的例子:
v0版本
1 package com.gavin.controller;
2
3 import org.springframework.web.bind.annotation.GetMapping;
4 import org.springframework.web.bind.annotation.RestController;
5
6 /**
7 * 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
6 /**
7 * 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
12 /**
13 * 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(){
99 //return separatorContext.getSeparator(boLangXianSeparator);
100 //return separatorContext.getSeparator(hengGangSeparator);
101 return separatorContext.getSeparator(greaterThanSeparator);
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調用。
V3版本
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
12 /**
13 * 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(){
54 //return separatorContext.getSeparator(boLangXianSeparator);
55 //return separatorContext.getSeparator(hengGangSeparator);
56 return separatorContext.getSeparator(greaterThanSeparator);
57 }
58
59
60 }
61
代碼運行如下:

從這4個版本中可以感受到,出現拼拼湊湊的感覺時,那么你的代碼就是內聚性比較低的表現了。如果代碼總要變來變去,其實是耦合高的表現。
最后,想要提高內聚性,可以通過降低耦合度來達到目的。在這兒,我個人提倡同學們編寫高內聚、低耦合的代碼。
【原創文章,轉載請注明出處! GavinMiao】
Gavin