一、單一職責(zé)原則(SRP)
單一職責(zé)原則(SRP)用于指導(dǎo)我們令境,在對功能劃分到具體的類中的時候杠园,要保證具有高內(nèi)聚性。對于SRP的一個很好的描述是:就一個類而言舔庶,應(yīng)該僅有一個引起它變化的原因抛蚁。
想要使用好SRP,一個首先要搞清楚的問題是:什么是職責(zé)栖茉?每一個職責(zé)都是變化的一個軸線篮绿,職責(zé)被定義為"變化的原因"。
這里對于職責(zé)的定義個人感覺比較清楚明確吕漂。我們要知道亲配,SRP中的職責(zé)并不是一個類中的函數(shù),而是一個變化的軸線惶凝,具體到不同層次的類吼虎,其職責(zé)有大有小。如對于一個Activity來說苍鲜,其負責(zé)的是展示一個完整的界面思灰,那么界面的內(nèi)容獲取勢必從他里面發(fā)起,界面的數(shù)據(jù)處理勢必在其里面完成混滔,一個Activity里面做的事情洒疚,調(diào)用的方法有很多,但是站在其層次的角度來考慮坯屿,一個Activity的職責(zé)就是負責(zé)好他所對應(yīng)的頁面油湖,而其他頁面的事情對它來說就是多余的職責(zé)。
即SRP告訴我們领跛,當(dāng)一個類中某幾個功能常常衍生出新的場景乏德、新的實現(xiàn)方式時,那么應(yīng)該考慮將他們各自進行獨立的職責(zé)封裝吠昭,而不是在當(dāng)前類中不斷的改來改去喊括、加來加去來使得當(dāng)前類變得臃腫、難以維護矢棚。
1郑什、一個例子
舉一個例子,假設(shè)需要裝一臺電腦蒲肋,以下是配置:
public class Computer {
private String CPU() {
return "最貴的最好的CPU";
}
private String board(){
return "最實惠的蹦误、型號對應(yīng)的主板";
}
private void build() {
String cpu = CPU();
String board = board();
return "電腦配置為"+cpu+board;
}
}
當(dāng)前來看這個類很單純劫拢,就是組裝一臺電腦嘛,完全可以勝任强胰,目前的方案是在CPU上花錢舱沧,主板挑一個過得去的就行侠鳄;但是雕崩,不同的人需求不一樣哟绊,有的人追求夠用即可在CPU上也追求實惠桑滩,有的人土豪一個蚂斤,在主板上也追求最貴最好彻犁。這樣的需求變動不得不使我們在Computer類中添加對應(yīng)的方法來滿足以上需求堂污,這樣就導(dǎo)致了Computer類的臃腫和難以維護妒蛇。
上述的問題在于恩脂,在可預(yù)見的未來帽氓,用戶選擇CPU的方案和選擇board的方案都會產(chǎn)生很多變化,即Computer類變化的軸線有兩個:CPU和board俩块,這違反了SRP黎休,所以方案就是將CPU的選擇方案封裝成一個單獨的類CPU,將board的選擇方案封裝成一個單獨的類Board玉凯。同時势腮,這個例子也體現(xiàn)了單一職責(zé)也是分層次的,Computer的職責(zé)是負責(zé)組裝電腦漫仆,CPU的職責(zé)是負責(zé)確定哪款CPU捎拯,Board的職責(zé)是負責(zé)確定哪款主板。
另外盲厌,在上述問題中CPU的選擇方案與主板之間還存在一種約束關(guān)系(型號對不上)署照,因此,如果不將職責(zé)進行拆分的話吗浩,build方法還可能因為耦合關(guān)系建芙,出現(xiàn)運行錯誤的情況,而這種情況當(dāng)代碼量很大時是很難察覺的拓萌。如果進行了職責(zé)的拆分,Computer類只負責(zé)CPU和board方案的匹配校驗工作升略,那么這種錯誤發(fā)生的概率就會降低很多微王。
2. 小結(jié)
通過上述例子我們可以粗略大膽的認為當(dāng)一下情況出現(xiàn)的時候,你的類違背了SRP品嚣,并且到了需要拆分的時候:
- 當(dāng)前類包含了多個子功能的具體實現(xiàn)炕倘;
- 在應(yīng)用場景中,上述子功能分別會有多種可能的實現(xiàn)方案翰撑。
二罩旋、開放封閉原則(OCP)
開閉原則(OCP):軟件實體應(yīng)該是可擴展的啊央,但是不可修改的。
按照OCP設(shè)計出的模塊具有兩個特征:
- 對擴展開放:模塊的行為是可以擴展的涨醋;當(dāng)應(yīng)用的需求改變時瓜饥,可以對模塊進行擴展來滿足新的行為。
- 對更改封閉:對模塊進行擴展時浴骂,不必改變模塊的源碼乓土。
上面的兩個特征看起來有些矛盾,不改變源碼怎么進行擴展溯警?實際上OCP要求我們使用抽象來定義行為趣苏,使用具體類來實現(xiàn)行為,擴展也就是說使用新的具體類來擴展行為梯轻;封閉也就是說在使用該行為時使用抽象類對象來囊括不同具體行為食磕,這樣一來就不用了更改源碼了(實際上由于多了個具體類,那么最起碼初始化該具體類對象的地方還是相當(dāng)于修改了源碼喳挑,這是沒法避免的)彬伦。
同時,還有一個重點在于蟀悦,在使用OCP來定義行為的時候媚朦,一定要選擇程序中呈現(xiàn)頻繁變化的那些部分進行抽象;如若不然日戈,那么濫用OCP也會帶來很高的維護成本询张。
小結(jié)
通過上面的說明,我們對OCP可以有一個大概的認知浙炼,即具有如下結(jié)構(gòu)的應(yīng)用:
- 定義類時采用抽象類份氧,實例化時采用具體類,即所謂的左類型與右類型弯屈。
三蜗帜、里式替換原則(LSP)
上述的OCP核心思想在于:利用抽象來定義行為,利用具體類來實現(xiàn)和實施不同種的行為资厉。但是厅缺,在面向?qū)ο箝_發(fā)中還有另一種形式的繼承機制,即父類并不是抽象類宴偿,在一些情況下父類可以勝任很多工作湘捎,但有時候需要子類對象來擴展一些工作,而不得不實例化子類對象窄刘,這時候就需要要求在實例化子類對象時窥妇,他要能夠完成其父類角色的任務(wù)。
即:在大多數(shù)情況下Parent a = new Parent();a.fun1();即可完成工作娩践,但有時候需要使用到子類:Parent b = new Child();b.fun1();b.fun2();才能夠完成工作活翩。那么就需要子類的fun1()方法能夠像父類的該方法一樣承擔(dān)應(yīng)有的工作烹骨。
LSP的解釋如下:子類型必須能夠替換掉他們的基類型。并且在替換掉基類型之后材泄,程序依然能夠運行沮焕。
1. 一個例子
假設(shè)父類的sort函數(shù)可以實現(xiàn)對數(shù)組的升序排序:
class Parent {
int[] array;
public Parent(int[] a) {
this.array = a;
}
public void sort() {
實現(xiàn)對array的升序排序
...
}
}
class Child {
...
@override
public void sort(){
實現(xiàn)對array的降序排序
}
}
很顯然,上述的子類雖然是繼承了父類脸爱,并且重寫了父類的sort方法遇汞,但是將全局的所有父類對象的實現(xiàn)變?yōu)樽宇悓ο螅敲闯绦蚩隙〞鰡栴}簿废。這就是LSP所約束的問題空入。
2. 小結(jié)
LSP和OCP看起來都是在說繼承的問題,但是他們所關(guān)注的場景不同族檬,而且可以看出OCP顯然是遵從了LSP的歪赢,因為OCP的背景為不同子類去擴展抽象類所定義的行為的。
而LSP則告訴我們单料,子類在覆蓋父類的方法的時候不能夠任意實現(xiàn)埋凯,而是要遵循父類對該方法的期望與要求。為此扫尖,有人提出了一種契約設(shè)計的方式:
- 契約是通過為每一個方法聲明的前置條件和后置條件來指定的白对;
- 要使一個方法得以執(zhí)行,前置條件必須要為真换怖;執(zhí)行完畢后甩恼,后置條件必須為真;
- 派生類的前置條件和后置條件的規(guī)則為:在重新聲明派生類中的例程時沉颂,只能使用相等或更弱的前置條件來替換基類的前置條件条摸;只能使用相等或更強的后置條件來替換基類的后置條件。
四铸屉、依賴倒置原則(DIP)
開門見山钉蒲,該原則的解釋為:
- 高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象彻坛;
- 抽象不應(yīng)該依賴于細節(jié)顷啼,細節(jié)應(yīng)該依賴于抽象。
如果高層模塊依賴于低層模塊昌屉,那么在不同的上下文中重用高層模塊會變得非常困難钙蒙。
首先要明確一點的是,既然是高層模塊怠益,那么其勢必要使用低層模塊提供的功能或者說服務(wù)仪搔,如何做到高層不依賴于低層呢瘾婿?答案就在后半句蜻牢,抽象烤咧,不過要加上一種要求:抽象由高層模塊來定義,低層模塊去實現(xiàn)它抢呆。這樣一來煮嫌,高層模塊就可以通過抽象類對象來使用想要的服務(wù),而不必理會低層模塊是如何實現(xiàn)它的抱虐,從而避免了對低層模塊的依賴昌阿。這也體現(xiàn)了第二個要求,是細節(jié)依賴抽象而不是抽象依賴細節(jié)恳邀,即高層模塊一旦定義了想要的服務(wù)懦冰,就不必理會低層模塊的具體實現(xiàn)方式,即這種服務(wù)一定要抽象到不用去理會實現(xiàn)細節(jié)谣沸。
很明顯刷钢,一個典型的例子就是計算機網(wǎng)絡(luò)協(xié)議的設(shè)計,高層協(xié)議通過服務(wù)訪問點來使用下層協(xié)議所提供的服務(wù)乳附,而不必理會下層協(xié)議是何種協(xié)議内地。例如TCP是傳輸層協(xié)議,其只需要下層能夠?qū)?shù)據(jù)傳輸?shù)蕉思纯筛吵魂P(guān)心你是IPv4還是IPv6.
五阱缓、接口隔離原則(ISP)
接口隔離原則很簡單:
不應(yīng)該強迫客戶依賴于他們不用的方法
因為一旦這些方法發(fā)生變化,他們不得不做出相應(yīng)的改變举农。
很顯然荆针,出現(xiàn)上述情況時,應(yīng)該對接口進行拆分了并蝗。