拯救爛慫代碼?我是這么做的
一、故事
這幾天的小貓心情還不錯,修完了"冪等事件的bug",填完了"緩存擊穿的坑",前兩天組長交代給他的“整理一份系統(tǒng)現(xiàn)狀報告”任務(wù)也算是有了思路,陰霾終于散去。好像一切都朝著好的方向發(fā)展了。是的,也該過去了,畢竟這些事情折磨小貓都快個把月了。
這天,小貓?zhí)嶂鴥杀Х日业搅水a(chǎn)品經(jīng)理,本來么,禮多人不怪,這不效果就來了么。
“......整個業(yè)務(wù)背景呢,其實也就是這樣了,然后后面有啥其他問題,也歡迎隨時問,知無不答”。產(chǎn)品老汪和小貓足足聊了一半個小時,唾沫星子橫飛,似乎還有點意猶未盡。
“嗯嗯,謝謝了,汪哥,耽誤你時間了。”
“沒事兒,不用客氣,不過提醒你一下,當(dāng)前系統(tǒng)經(jīng)過太多開發(fā)的手了,系統(tǒng)代碼得好好看看,聽說初版本的時候都是外包搞的。你懂得......”
“嗯嗯,好,太感謝了” 小貓連連點頭,老汪的話倒是提醒了他。
內(nèi)心開始嘀咕,“哎??磥砀愣ǜ鞣N模型關(guān)系,業(yè)務(wù)背景也還是不行啊,面對現(xiàn)實吧,爛慫代碼還是得梳理一下的,當(dāng)前系統(tǒng)的接口定義、以及類的封裝貌似都挺亂的......”
二、開啟“類圖”之旅
說到對系統(tǒng)代碼中的模型梳理,其實最好的方式還是使用UML類圖。上個章節(jié)中老貓沒有展開和大家分享UML類圖,一個是由于篇幅的原因,第二個是老貓覺得類圖對于咱們后續(xù)閱讀框架源碼以及底層設(shè)計模式有著相當(dāng)大的幫助,所以很有必要將其作為單獨一篇來和大家分享。如下概要:
1.類圖的簡介
類圖是什么呢?比較專業(yè)一點的說法:在軟件工程中,類圖是一種靜態(tài)的結(jié)構(gòu)圖,描述了系統(tǒng)的類的集合,類的屬性和類之間的關(guān)系,可以簡化了人們對系統(tǒng)的理解;類圖是系統(tǒng)分析和設(shè)計階段的重要產(chǎn)物,是系統(tǒng)編碼和測試的重要模型。
其實不僅僅是軟件工程中,其他很多時候需要理清復(fù)雜關(guān)系的時候,咱們也可以用到這種表示方式。接下來,跟著老貓揭開類圖的神秘面紗......
2.類圖的基本屬性
其實類圖還是相當(dāng)簡單的,類圖只要概括起來就兩個比較重要的點,一個是類,第二個是類和類之間的關(guān)系,咱們弄清楚這兩個,其實類圖也就掌握了。啥是類?對于OO程序員來說,這個類不就是咱們?nèi)粘6x的class么,沒錯,其實就是這概念。其中包含類名、類的屬性、類的方法。
王者榮耀打過吧?老貓用自己比較喜歡的一個英雄“鐘馗”為案例和大家解析一下。如下圖:
classDemo
上圖中咱們可以看到:
- 類名:最上面的矩形框中即為類名:鐘馗(Zhongkui)
- 類的屬性:類名下的第一個矩形圖中表示類的屬性,如上鐘馗案例中,其名稱、血條等等信息即為其屬性。
- 類的方法:在類屬性的下面就是類的方法,其中在鐘馗這個英雄類的方法中包含勾人--hook(),錘人--hammer()等等
上圖中我們可以看到無論是類的前面還是方法的前面其實都有不同的符號,其實這類符號就是作用域的概念。在類圖中,作用域主要有以下幾個:
- “+”:表示public,對所有的類都可見。
- “-”:表示private,僅僅只對當(dāng)前類可見。
- “#”:表示protected,受保護(hù)的屬性或者方法,其子孫類可見。
- “~”:表示package,表示包內(nèi)可見
- “=”:表示默認(rèn)值,上述案例中可能沒有畫出來。大家概念知道一下即可。
- “_”: 下劃線,表示當(dāng)前的這個類的方法或者屬性是靜態(tài)的。
- 斜體:老貓這里沒有畫出來,這里其實表示的是抽象,當(dāng)然有的時候也會用兩個尖括號包裹來表示抽象,<<我是抽象類or接口>>。
方法和屬性冒號后面所表示的含義也不一樣,方法后面冒號后面的描述例如hammer():boolean,表示的就是返回值為boolean類型,是否錘到人。而屬性的冒號后面則表示的是當(dāng)前該屬性的類型。
3.類之間的關(guān)系
我們再來看一個每個類和類之間的關(guān)系。直接看表示方式,如下圖:
classRel
上述圖中,咱們可能需要記住的是各種線條的表示方式。接下來,咱們一一來看。
(1) 泛化(繼承)關(guān)系
我們舉一個鴨子繼承鳥類這樣一個例子。我們表示出來的話,如下:
jc
表示方法:空心三角+實線,箭頭指向父類
上圖中我們清晰地看見子類鴨子繼承了父類所有的屬性,當(dāng)然除此之外,子類還豐富了父類的方法,例如鴨子繼承下蛋的同時擴充了游泳。
小代碼:
public class Bird {
public String feather;
public String wing;
public void layEggs() {
}
}
public class Duck extends Bird {
public String feather;
public String wing;
public void layEggs() {
}
public boolean swim() {
return true;
}
}
(2) 實現(xiàn)
抽象了飛翔這么一個接口,然后讓各種不一樣的鳥類進(jìn)行實現(xiàn),如下圖:
classImpl
表示方法:空心三角+虛線,箭頭指向待實現(xiàn)的接口
表示實現(xiàn)接口中的所有的方法。
小代碼:
public interface Fly {
public void doFly();
}
public class WildGoose implements Fly {
public void doFly() {
}
}
(3) 依賴
舉個例子,動物都依賴于空氣,于是咱們就有了下圖:
classyl
表示方法:尖括號+虛線,尖括號指向被依賴的類。
依賴關(guān)系表示一個類使用(依賴)另一個類的服務(wù)或信息。一般來說,依賴總是單向的,不應(yīng)該存在雙向依賴。就拿上面的例子來說,動物依賴于空氣,咱們并不能說,空氣依賴于動物,可見依賴是單向的。
小代碼:
public class Animal {
public void breath(Oxygen oxygen,Water water) {
oxygen.supply();
water.supply();
}
}
public class Oxygen {
public void supply(){
}
}
(4) 關(guān)聯(lián)
說到關(guān)聯(lián),種類比較多,但是理解起來還是比較簡單,即一類對象和另外一類對象之間存在某種關(guān)系。這種關(guān)聯(lián)關(guān)系分為單項關(guān)聯(lián)、雙向關(guān)聯(lián)、自關(guān)聯(lián)、多重關(guān)聯(lián)。在面向?qū)ο笳Z言中,如果表示存在關(guān)聯(lián)關(guān)系,那么通常會將一個類的對象作為另外一個類的成員變量。
①單向關(guān)聯(lián)
例如用戶在商城中購買東西之前都需要去設(shè)置地址,那么用戶和地址之間就是單向關(guān)聯(lián)關(guān)系。
單項關(guān)聯(lián)
表示方法:尖括號+實線,尖括號指向用戶的成員變量,即地址。
小代碼:
public class User {
private Address address;
...
}
public class Address{
...
}
②雙向關(guān)聯(lián)
例如用戶購買商品,商品同時也會被不同的人購買,那么這種行為就是雙向關(guān)聯(lián)。
雙向關(guān)聯(lián)
表示方法:實線連接兩個類即可。
小代碼:
public class User {
private List<Product> products;
...
}
public class Product{
private User user;
}
③自關(guān)聯(lián)
關(guān)于自關(guān)聯(lián),其實我們?nèi)粘i_發(fā)中也遇到過,尤其是一些遞歸類的時候,例如樹形結(jié)構(gòu),大樹下面套小樹。
樹形Tree
小代碼:
public class Tree {
private Tree subTree;
}
④多重關(guān)聯(lián)
多重關(guān)聯(lián)關(guān)系表示兩個關(guān)聯(lián)對象在數(shù)量上的對應(yīng)關(guān)系。在UML中,對象之間的多重性可以直接在關(guān)聯(lián)直線上用一個數(shù)字或一個數(shù)字范圍表示。比如一條河上在某個時間有好幾只鴨子在上面浪,當(dāng)然也可以沒有。但是在那個時刻對于那只鴨子而言,它只能在一條河里面浪。例子以及數(shù)量表示說明如下:
多重
小代碼:
private class River {
private Duck[] ducks;
....
}
public class Duck {
}
(5) 聚合
主要描述聚合關(guān)系,描述的是整體與部分的關(guān)系。對于聚合來說,成員對象是整體對象的一部分,當(dāng)然成員對象也可以脫離整體獨立存在。這么說的話有點抽象了,打個比方,老貓有最近想要組裝一臺臺式電腦,于是我買了顯卡以及主板等等元器件。對于臺式機來說顯卡以及元器件是其一部分,但是顯卡以及主板又可以單獨作為商品進(jìn)行售賣。于是我們就有了下面這樣一幅示意圖。
聚合
表示方法:空心菱形表示連接待聚合的那個類對象
小代碼:
public class Computer {
private KeyBoard keyboard;
//構(gòu)造方法注入
public Computer(KeyBoard keyboard){
this.keyBoard = keyboard;
}
}
public class KeyBoard {
}
(6) 組合
組合關(guān)系和聚合有點類似,也是描述整體和部分的關(guān)系,區(qū)別是聚合的話成員對象可以脫離整體單獨存在,但是組合是"同生共死"的組合對象關(guān)系。例如公司和部門之間的關(guān)系。即為組合關(guān)系。如果公司都涼涼了,那么還有部門么?簡單一幅示意圖如下。
組合
表示方法:實心菱形表示連接待組合的那個類對象
小代碼:
public class Company {
private Department department;
public Company(Department department){
this.department = department;
}
}
public class Department {
}
4.綜合案例
若是看到此處,相信大家對類圖的各種表示方式已經(jīng)了然于胸了吧。那么咱們接下來就來看看老貓繪制出來的類圖關(guān)系吧。
案例
大家可以試試看看對照著老貓上面的梳理來讀懂這樣一個真實的模型類圖吧。
5.如何繪制類圖
寫了這么多,相信大家對如何繪制類圖用什么工具還是比較好奇的。
其實在idea中很多時候我們可以直接查看類圖。只要在類名上面右鍵單擊,選擇 Diagrams -> Show Diagram即可展示如下圖:
demo
在上一篇文章中也有很多小伙伴問老貓流程圖是用什么畫的。其實都一樣的,老貓一般繪制這種圖就用兩種工具軟件,一種是drawio,另外一種是wps中的流程圖繪制。當(dāng)然其實還有其他繪制工具,例如process on等等,大家可以自己去試試。
三、寫在最后
為了讓小貓更好地優(yōu)化梳理爛慫代碼,老貓花了好幾個晚上整理出來了繪制的方法。其實無論是多么復(fù)雜的類,只要我們把握清楚其中的類圖關(guān)系,然后再結(jié)合上一篇文章中的業(yè)務(wù)模型對照起來一起看,就很清晰了。當(dāng)然,前提是需要有足夠的耐心。
當(dāng)然老貓費勁心血梳理uml的類圖繪制流程其實還有一個原因,就是接下來咱們要開啟“有趣的設(shè)計模式之旅”了。小伙伴們,持續(xù)關(guān)注老貓吧,相信后文更精彩。