設(shè)計(jì)模式也反復(fù)看了一段日子了,但是疏于使用的話壕翩,時(shí)間久了還是會(huì)弄混其中的一些模式蛉迹,為了加深理解,特此開設(shè)專題進(jìn)行總結(jié)放妈,主要是方便自己查閱北救,所以會(huì)比較簡練荐操,就不投稿了,無意中進(jìn)來的小伙伴們看不明白的話也請?jiān)彙?/p>
先簡單粗暴的羅列一下概念
1.單一職責(zé)原則:描述的意思是每個(gè)類都只負(fù)責(zé)單一的功能珍策,切不可太多托启,并且一個(gè)類應(yīng)當(dāng)盡量的把一個(gè)功能做到極致。
2.里氏替換原則:這個(gè)原則表達(dá)的意思是一個(gè)子類應(yīng)該可以替換掉父類并且可以正常工作攘宙。(這個(gè)最難理解屯耸,需仔細(xì)體會(huì))
3.接口隔離原則:也稱接口最小化原則,強(qiáng)調(diào)的是一個(gè)接口擁有的行為應(yīng)該盡可能的小蹭劈。
4.依賴倒置原則:這個(gè)原則描述的是高層模塊不該依賴于低層模塊疗绣,二者都應(yīng)該依賴于抽象,抽象不應(yīng)該依賴于細(xì)節(jié)铺韧,細(xì)節(jié)應(yīng)該依賴于抽象多矮。
5.迪米特原則:也稱最小知道原則,即一個(gè)類應(yīng)該盡量不要知道其他類太多的東西哈打,不要和陌生的類有太多接觸塔逃。
6.開-閉原則:最后一個(gè)原則,一句話料仗,對修改關(guān)閉湾盗,對擴(kuò)展開放。
下面說一下對各個(gè)原則的理解罢维。
單一職責(zé) 原則淹仑,從名字就可以看出來,就是使職責(zé)單一化肺孵,只負(fù)責(zé)干一件事情匀借,可以聯(lián)系到國家政府機(jī)構(gòu)的劃分
大體分了如圖這些機(jī)構(gòu),每個(gè)機(jī)構(gòu)又劃分為很多個(gè)科室平窘,每個(gè)科室中的人也分管不同的工作吓肋,這樣設(shè)計(jì)的目的也是使職責(zé)單一化,提高運(yùn)作效率瑰艘。如果一個(gè)人負(fù)責(zé)的工作過多是鬼,不但增加了這個(gè)人的負(fù)擔(dān)而且也更容易出錯(cuò),想找這個(gè)人辦事兒的人太多紫新,也提高了復(fù)雜度和耦合均蜜。反觀到j(luò)ava的類中也是如此,所以單一職責(zé)原則是我覺得六大原則當(dāng)中最應(yīng)該遵守的原則芒率。
里氏替換原則簡單說來就是子類可以擴(kuò)展父類的功能囤耳,但不能改變父類原有的功能。
它包含以下4層含義:
1 子類可以實(shí)現(xiàn)父類的抽象方法,但不能覆蓋父類的非抽象方法充择。
2 子類中可以增加自己特有的方法德玫。
3 當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松椎麦。
4 當(dāng)子類的方法重載父類的方法時(shí)宰僧,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。
里氏替換原則的關(guān)鍵點(diǎn)在于不能覆蓋父類的非抽象方法观挎。父類中凡是已經(jīng)實(shí)現(xiàn)好的方法琴儿,實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類必須遵從這些規(guī)范键兜,但是如果子類對這些非抽象方法任意修改凤类,就會(huì)對整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義普气。
關(guān)于第3點(diǎn)做一下說明:
簡單理解:為什么是放大谜疤?因?yàn)楦割惙椒ǖ膮?shù)類型相對較小,所以當(dāng)傳入父類方法的參數(shù)類型现诀,重載的時(shí)候優(yōu)先匹配父類的方法夷磕,而子類的重載方法不會(huì)匹配,因此仍保證執(zhí)行父類的方法(子類繼承的時(shí)候其實(shí)操作的是子類中的父類成分)仔沿,所以業(yè)務(wù)邏輯不會(huì)改變坐桩。
關(guān)于第4點(diǎn)做一下說明:
簡單理解:如果是重載,由于前置條件的要求封锉,會(huì)調(diào)用到父類的函數(shù)绵跷,因此子函數(shù)不會(huì)被調(diào)用。
如果是覆蓋成福,則調(diào)用子類的函數(shù)碾局,這時(shí)子類的返回值比父類要求的小。因?yàn)楦割愓{(diào)用函數(shù)的時(shí)候奴艾,返回值的類型是父類的類型净当,而子類的返回值更小,賦值合法蕴潦。
Father F = ClassF.Func();//;用子類替換時(shí)Father F = ClassC.Func()是合法的 子類賦值父類轉(zhuǎn)是合法的像啼,父類賦值給子類是不合法的。
方法中的輸入?yún)?shù)稱為前置條件潭苞,這是什么意思呢忽冻?大家做過Web Service開發(fā)就應(yīng)該知道有一個(gè)“契約優(yōu)先”的原則,也就是先定義出WSDL接口此疹,制定好雙方的開發(fā)協(xié)議甚颂,然后再各自實(shí)現(xiàn)蜜猾。里氏替換原則也要求制定一個(gè)契約,就是父類或接口振诬,這種設(shè)計(jì)方法也叫做Design by Contract,契約設(shè)計(jì)衍菱,是與里氏替換原則融合在一起的赶么。契約制定了,也就同時(shí)制定了前置條件和后置條件脊串,前置條件就是你要讓我執(zhí)行辫呻,就必須滿足我的條件;后置條件就是我執(zhí)行完了需要反饋琼锋,標(biāo)準(zhǔn)是什么放闺。這個(gè)比較難理解,使用一個(gè)網(wǎng)友的例子
public class Father {
public void func(HashMap m){
System.out.println("執(zhí)行父類...");
}
}
import java.util.Map;
public class Son extends Father{
public void func(Map m){//方法的形參比父類的更寬松
System.out.println("執(zhí)行子類...");
}
}
import java.util.HashMap;
public class Client{
public static void main(String[] args) {
//引用基類的地方能透明地使用其子類的對象缕坎。
//Father f = new Father();
Son f = new Son();
HashMap h = new HashMap();
f.func(h);
}
}
輸出 :執(zhí)行父類...
ps:這里引申一個(gè)Java重載方法匹配優(yōu)先級問題
寫出以下程序的輸出:
public class Overload {
public static void say(long arg) {
System.out.println("hello long");
}
public static void say(Character arg) {
System.out.println("hello character");
}
public static void say(char... arg) {
System.out.println("hello char...");
}
// Serializable 參數(shù)
public static void say(Serializable arg) {
System.out.println("hello serializable");
}
public static void main(String[] args) {
say('a');
}
}
答案: hello long
這條題目考的是重載方法匹配的優(yōu)先級怖侦,那么它的匹配優(yōu)先級是怎樣的呢?
我們可以擴(kuò)充一下這個(gè)程序谜叹,加入一些其他的參數(shù)匾寝,然后測試一下:
public class Overload {
// Object 參數(shù)
public static void say(Object arg) {
System.out.println("hello object");
}
// int 參數(shù)
public static void say(int arg) {
System.out.println("hello int");
}
// long 參數(shù)
public static void say(long arg) {
System.out.println("hello long");
}
// char 參數(shù)
public static void say(char arg) {
System.out.println("hello char");
}
// Character 參數(shù)
public static void say(Character arg) {
System.out.println("hello character");
}
// 變長參數(shù)
public static void say(char... arg) {
System.out.println("hello char...");
}
// Serializable 參數(shù)
public static void say(Serializable arg) {
System.out.println("hello serializable");
}
public static void main(String[] args) {
say('a');
}
}
如果直接運(yùn)行的話,毫無疑問荷腊,輸出為: hello char
如果將char參數(shù)的函數(shù)注釋之后艳悔,會(huì)輸出什么呢?
答案是:hello int
因?yàn)檫@期間女仰,字符a發(fā)生了一次自動(dòng)轉(zhuǎn)型猜年,它除了能夠表示字符a外,還能表示數(shù)字65疾忍,于是重載方法匹配了int參數(shù)的重載方法乔外。
現(xiàn)在我們再將這個(gè)方法注釋了,輸出的結(jié)果大家應(yīng)該知道是什么了吧锭碳?
那就是:hello long
原因就是int自動(dòng)轉(zhuǎn)型為long袁稽。其實(shí)還可以轉(zhuǎn)化為float和double的,但不能轉(zhuǎn)化為byte和short擒抛,因?yàn)閏har到這兩個(gè)類型的轉(zhuǎn)化是不安全的推汽,這幾個(gè)類型的轉(zhuǎn)化優(yōu)先級為:char->int->long->float->double。
好歧沪,我們再繼續(xù)注釋掉這個(gè)函數(shù)歹撒,然后輸出是什么呢?
答案:hello character
為什么诊胞?大家應(yīng)該知道Java里面為每種基本數(shù)據(jù)類型都提供一種封裝類型吧暖夭?char對應(yīng)的就是Character锹杈,所以調(diào)用函數(shù)期間,當(dāng)找不到基本類型轉(zhuǎn)化的匹配之后迈着,char就會(huì)發(fā)生一次自動(dòng)裝箱竭望,變成了Character類型。
根本停不下來啊裕菠,再繼續(xù)注釋了它咬清,看下輸出。
輸出:hello serializable
這什么東西嘛奴潘。旧烧。。怎么會(huì)輸出這個(gè)家伙啊画髓。掘剪。。奈虾。原來是因?yàn)镃haracter實(shí)現(xiàn)了Serializable接口夺谁,當(dāng)它找不到匹配的類型之后,就會(huì)找它所實(shí)現(xiàn)的接口愚墓。但是予权,如果我們再增加一個(gè)重載函數(shù):
public static void say(Comparable arg) {
System.out.println("hello Comparable");
}
那么就會(huì)報(bào)錯(cuò)了, 因?yàn)镃haracter實(shí)現(xiàn)了Serializable和Comparable這兩個(gè)接口浪册,而接口匹配的優(yōu)先級是一樣的扫腺,編譯器無法判斷轉(zhuǎn)型為哪種類型,提示類型模糊村象,拒絕編譯笆环。
好,繼續(xù)注釋掉Serializable參數(shù)的函數(shù)厚者,看輸出:hello object
接口找不到匹配之后躁劣,就會(huì)開始找匹配的父類,優(yōu)先級是順著繼承鏈库菲,由下往上進(jìn)行匹配账忘。
最后,連這個(gè)函數(shù)也注釋了的話熙宇,大家應(yīng)該知道輸出的是什么了吧鳖擒?
當(dāng)然就是:hello char...
由此可見,變長參數(shù)的優(yōu)先級是最低的烫止。
接口隔離原則 講一個(gè)極端點(diǎn)的例子蒋荚,平常很多人常說,原來上學(xué)的時(shí)候?qū)W的那些個(gè)東西有啥用馆蠕,生活中也用不到期升,先不說這種觀點(diǎn)正確與否惊奇,我們以這種想法去想象一下,如果把人所具有的技能定義為一個(gè)接口播赁,以上的觀點(diǎn)就是往這個(gè)接口中定義了 語文 英語 數(shù)學(xué) 物理 化學(xué) 等等方法颂郎,有的人可能一輩子沒學(xué)過英語,那么這個(gè)方法對于這些人來說就是個(gè)不必有實(shí)現(xiàn)的方法行拢,但接口這偏偏定義了祖秒,那可能就會(huì)屈就的實(shí)現(xiàn)成一個(gè)空方法,這還沒什么舟奠,最嚴(yán)重的是會(huì)給使用者造成假象,即這個(gè)實(shí)現(xiàn)類擁有接口中所有的行為房维,結(jié)果調(diào)用方法時(shí)卻沒收獲到想要的結(jié)果沼瘫。你和他說hello,他也不會(huì)跟你回答 world!
所以定義接口的時(shí)候咙俩,其中的方法要是實(shí)現(xiàn)這個(gè)接口的類所共有的才好耿戚。
依賴倒置原則 相對于細(xì)節(jié)的多變性,抽象的東西要穩(wěn)定的多阿趁。以抽象為基礎(chǔ)搭建起來的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來的架構(gòu)要穩(wěn)定的多膜蛔。在java中,抽象指的是接口或者抽象類脖阵,細(xì)節(jié)就是具體的實(shí)現(xiàn)類皂股,使用接口或者抽象類的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作命黔,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類去完成呜呐。
class Newspaper implements IReader {
public String getContent(){
return "林書豪17+9助尼克斯擊敗老鷹……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一個(gè)阿拉伯的故事……";
}
}
class Mother{
public void narrate(IReader reader){
System.out.println("媽媽開始講故事");
System.out.println(reader.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}
迪米特原則應(yīng)該將細(xì)節(jié)全部高內(nèi)聚于類的內(nèi)部,其他的類只需要知道這個(gè)類主要提供的功能即可悍募。 所謂高內(nèi)聚就是盡可能將一個(gè)類的細(xì)節(jié)全部寫在這個(gè)類的內(nèi)部蘑辑,不要漏出來給其他類知道,否則其他類就很容易會(huì)依賴于這些細(xì)節(jié)坠宴,這樣類之間的耦合度就會(huì)急速上升洋魂,這樣做的后果往往是一個(gè)類隨便改點(diǎn)東西,依賴于它的類全部都要改喜鼓。
迪米特原則雖說是指的一個(gè)類應(yīng)當(dāng)盡量不要知道其他類太多細(xì)節(jié)副砍,但其實(shí)更重要的是一個(gè)類應(yīng)當(dāng)不要讓外部的類知道自己太多。兩者是相輔相成的颠通,只要你將類的封裝性做的很好址晕,那么外部的類就無法依賴當(dāng)中的細(xì)節(jié)。
開閉原則 這個(gè)原則更像是前五個(gè)原則的總綱顿锰,前五個(gè)原則就是圍著它轉(zhuǎn)的谨垃,只要我們盡量的遵守前五個(gè)原則启搂,那么設(shè)計(jì)出來的系統(tǒng)應(yīng)該就比較符合開閉原則了,相反刘陶,如果你違背了太多胳赌,那么你的系統(tǒng)或許也不太遵循開閉原則。