詳解java設計模式之六大原則
一、單一職責原則
1、單一職責定義
單一職責原則:一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。
單一職責原則告訴我們:一個類不能太“累”!在軟件系統(tǒng)中,一個類承擔的職責越多,它被復用的可能性就越小,而且一個類承擔的職責過多,就相當于將這些職責耦合在一起,當其中一個職責
變化時,可能會影響其他職責的運作,因此要將這些職責進行分離,將不同的職責封裝在不同的類中,即將不同的變化原因封裝在不同的類中,如果多個職責總是同時發(fā)生改變則可將它們封裝在同一類中。
2、單一職責優(yōu)點
1)降低了類的復雜度。一個類只負責一項職責比負責多項職責要簡單得多。
2) 提高了代碼的可讀性。一個類簡單了,可讀性自然就提高了。
3) 提高了系統(tǒng)的可維護性。代碼的可讀性高了,并且修改一項職責對其他職責影響降低了,可維護性自然就提高了。
4) 變更引起的風險變低了。單一職責最大的優(yōu)點就是修改一個功能,對其他功能的影響顯著降低。
3、案例說明
在網上找了個比較好理解,也比較符合實際開發(fā)中用來思考的小案例。
有一個用戶類,我們先看它的接口:

這個接口是可以優(yōu)化的,用戶的屬性(Property)和用戶的行為(Behavior)沒有分開,這是一個嚴重的錯誤!非常正確,這個接口確實設計得一團糟,應該把用戶的信息抽取成一個BO(Bussiness Object,業(yè)務對象),把行為抽取成一個BIZ(Business Logic,業(yè)務邏輯),按照這個思路對類圖進行修正,如圖1-2所示。

重新拆封成兩個接口,IUserBO負責用戶的屬性,簡單地說,IUserBO的職責就是收集和反饋用戶的屬性信息;IUserBiz負責用戶的行為,完成用戶信息的維護和變更。
然后IUserInfo來實現這兩個接口,重寫方法。
代碼清單1-1 分清職責后的代碼示例
.......
IUserBiz userInfo = new UserInfo();
//我要賦值了,我就認為它是一個純粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword("abc");
//我要執(zhí)行動作了,我就認為是一個業(yè)務邏輯類
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();
.......
思考:上面這樣是單一職責原則嗎?當然不是了,你實現了兩個接口,不還是把行為和屬性寫在一個類了,和最上面又有什么區(qū)別呢,這里只能說實現了接口隔離原則(下面會說)
那如何來確保單一原則,在實際的使用中,我們更傾向于使用兩個不同的類:一個是IUserBO, 一個是IUserBiz很簡單如圖所示:

4、自己理解
單一職責原則有兩個難點:
1) 職責劃分:
一個職責一個接口,但問題是“職責”是一個沒有量化的標準,一個類到底要負責那些職責?這些職責該怎么細化?細化后是否都要有一個接口或類?這些都需要從實際的項目去考慮。
比如上面寫成一個類他的單一職責就是修改用戶信息,為什么一定要分修改行為和修改屬性。那是不是又可以在細分修改密碼和修改屬性呢?
2)類的冗余
如果可以追求單一職責也是沒有必要的,本來一個類可以搞定的實現,如果非得修改用戶名一個類,修改密碼一個類來實現單一原則,這樣也會讓你的類變得非常多,反而不容易維護。
我自己的感悟:
1)首先要培養(yǎng)單一職責的思想,特別是如果代碼可以復用的情況下經常思考能不能用單一職責原則來劃分類。
2) 類的單一職責實現在好多時候并不切實際,但是方法上一定要保持單一職責原則。比如你修改密碼的方法就是用來修改密碼。這樣做有個很大的好處就是便于代碼調試,容易將代碼的Bug找出來,一個方法只完成
一件事情,相對調試能簡單很多,讓其他人員能更快更好的讀懂代碼、理解這個類或者方法的功能。
二、里氏代換原則
這個和單一職責原則比起來,顯然就好理解多了,而且也不那么模糊不清。
1、定義
官方定義:所有引用基類(父類)的地方必須能透明地使用其子類的對象。
簡單理解就是:子類一般不該重寫父類的方法,因為父類的方法一般都是對外公布的接口,是具有不可變性的,你不該將一些不該變化的東西給修改掉。
是不是感覺這個原則不太招人喜歡,因為我們在寫代碼的時候經常會去重寫父類的方法來滿足我們的需求。而且在模板方法模式,缺省適配器,裝飾器模式等一些設計模式都會采用重寫父類的方法。
怎么說呢,里氏代換原則的主要目的主要是防止繼承所帶來的弊端。
繼承的弊端:
繼承作為面向對象三大特性之一,在給程序設計帶來巨大便利的同時,也帶來了弊端。
繼承會增加了對象間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,并且父類修改后,所有涉及到子類的功能都有可能會產生故障。
2、案例說明
SomeoneClass類,其中有一個方法,調用了某一個父類的方法。
//某一個類
public class SomeoneClass {
//有某一個方法,使用了一個父類類型
public void someoneMethod(Parent parent){
parent.method();
}
}
父類代碼
public class Parent {
public void method(){
System.out.println("parent method");
}
}
SubClass子類把父類的方法給覆蓋。
public class SubClass extends Parent{
//結果某一個子類重寫了父類的方法,說不支持該操作了
public void method() {
throw new UnsupportedOperationException();
}
}
測試類
/**這個異常是運行時才會產生的,也就是說,我的SomeoneClass并不知道會出現這種情況,結果就是我調用下面這段代碼的時候,
*本來我們的思維是Parent都可以傳給someoneMethod完成我的功能,我的SubClass繼承了Parent,當然也可以了,但是最終這個調用會拋出異常。
*/
public class Client {
public static void main(String[] args) {
SomeoneClass someoneClass = new SomeoneClass();
someoneClass.someoneMethod(new Parent());
someoneClass.someoneMethod(new SubClass());
}
}
這就相當于埋下了一個個陷阱,因為本來我們的原則是,父類可以完成的地方,我用子類替代是絕對沒有問題的,但是這下反了,我每次使用一個子類替換一個父類的時候,我還要擔心這個
子類有沒有給我埋下一個上面這種炸彈。
3、自己理解
感覺自己在開發(fā)中不太會出現上面這么愚蠢的錯誤。理由:
1)自己水平有限,平時在開發(fā)中使用繼承的時候都是基礎API的類然后重寫,很少繼承自己寫的類,一般都是實現接口比較多。
2)第二就算我用了繼承,我在傳參的時候我只要稍微注意下就應該知道這個方法的參數是Parent,而如果我要放入SubClass時,就應該考慮自己有沒有重寫這個方法,如果重寫這樣肯定不行。所以也不多發(fā)生上面的錯誤了。
所以總的來說,要知道繼承的這個隱患,在開發(fā)中注意就是。
三、接口隔離原則
1、定義
當一個接口太大時,我們需要將它分割成一些更細小的接口,使用該接口的客戶端僅需知道與之相關的方法即可。
為什么要這么做呢?
其實很好理解,因為你實現一個接口就是實現它所有的方法,但其實你并不需要它的所有方法,那就會產生:一個類實現了一個接口,里面很多方法都是空著的,只有個別幾個方法實現了。
這樣做不僅會強制實現的人不得不實現本來不該實現的方法,最嚴重的是會給使用者造成假象,即這個實現類擁有接口中所有的行為,結果調用方法時卻沒收獲到想要的結果。
2、案例說明
比如我們設計一個手機的接口時,就要手機哪些行為是必須的,要讓這個接口盡量的小,或者通俗點講,就是里面的行為應該都是這樣一種行為,就是說只要是手機,你就必須可以做到的。
下面是手機接口。
public interface Mobile {
public void call();//手機可以打電話
public void sendMessage();//手機可以發(fā)短信
public void playBird();//手機可以玩憤怒的小鳥?
}
上面第三個行為明顯就不是一個手機必須有的,那么上面這個手機的接口就不是最小接口,假設我現在的非智能手機去實現這個接口,那么playBird方法就只能空著了,因為它不能玩。
3、自己理解
這個沒啥說的,很好理解,最上面我寫單一職責原則的時候的那個案例,中間那部分就是接口隔離原則。這個思想自己要慢慢培養(yǎng),然后更多的運用到實際開發(fā)中去。
四、依賴倒置原則
1、定義
依賴倒置原則包含三個含義
1) 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象
2) 抽象不應該依賴細節(jié)
3)細節(jié)應該依賴抽象
2、案例說明
大家都喜歡閱讀,閱讀文學經典滋潤自己的內心心靈,下面是小明同學閱讀文學經典的一個類圖

文學經典類
//文學經典類
public class LiteraryClassic{
//閱讀文學經典
public void read(){
System.out.println("文學經典閱讀,滋潤自己的內心心靈");
}
}
小明類
//小明類
public class XiaoMing{
//閱讀文學經典
public void read(LiteraryClassic literaryClassic){
literaryClassic.read();
}
}
場景類
public class Client{
public static void main(Strings[] args){
XiaoMing xiaoming = new XiaoMing();
LiteraryClassic literaryClassic = new LiteraryClassic();
//小明閱讀文學經典
xiaoming.read(literaryClassic);
}
}
看,我們的實現,小明同學可以閱讀文學經典了。
小明同學看了一段文學經典后,忽然他想看看看小說來放松一下自己,我們實現一個小說類:
小說類
//小說類
public class Novel{
//閱讀小說
public void read(){
System.out.println("閱讀小說,放松自己");
}
}
現在我們再來看代碼,發(fā)現XiaoMing類的read方法只與文學經典LiteraryClassic類是強依賴,緊耦合關系,小明同學竟然閱讀不了小說類。這與現實明顯的是不符合的,代碼設計的是有問題的。那么問題在那里呢?
我們看小明類,此類是一個高層模塊,并且是一個細節(jié)實現類,此類依賴的是一個文學經典LiteraryClassic類,而文學經典LiteraryClassic類也是一個細節(jié)實現類。這是不是就與我們說的依賴倒置原則相違背呢?
依賴倒置原則是說我們的高層模塊,實現類,細節(jié)類都應該是依賴與抽象,依賴與接口和抽象類。
為了解決小明同學閱讀小說的問題,我們根據依賴倒置原則先抽象一個閱讀者接口,下面是完整的uml類圖:

IReader接口:
public interface IReader{
//閱讀
public void read(IRead read){
read.read();
}
}
再定義一個被閱讀的接口IRead
public interface IRead{
//被閱讀
public void read();
}
再定義文學經典類和小說類
文學經典類:
//文學經典類
public class LiteraryClassic implements IRead{
//閱讀文學經典
public void read(){
System.out.println("文學經典閱讀,滋潤自己的內心心靈");
}
}
小說類
//小說類
public class Novel implements IRead{
//閱讀小說
public void read(){
System.out.println("閱讀小說,放松自己");
}
}
再實現小明類
//小明類
public class XiaoMing implements IReader{
//閱讀
public void read(IRead read){
read.read();
}
}
然后,我們再讓小明分別閱讀文學經典和小說
public class Client{
public static void main(Strings[] args){
XiaoMing xiaoming = new XiaoMing();
IRead literaryClassic = new LiteraryClassic();
//小明閱讀文學經典
xiaoming.read(literaryClassic);
IRead novel = new Novel();
//小明閱讀小說
xiaoming.read(novel);
}
}
至此,小明同學是可以閱讀文學經典,又可以閱讀小說了,目的達到了。
為什么依賴抽象的接口可以適應變化的需求?這就要從接口的本質來說,接口就是把一些公司的方法和屬性聲明,然后具體的業(yè)務邏輯是可以在實現接口的具體類中實現的。所以我們當依賴
對象是接口時,就可以適應所有的實現此接口的具體類變化。
3、依賴的三種方法
依賴是可以傳遞,A對象依賴B對象,B又依賴C,C又依賴D,……,依賴不止。只要做到抽象依賴,即使是多層的依賴傳遞也無所謂懼。
1)構造函數傳遞依賴對象
在類中通過構造函數聲明依賴對象,按照依賴注入的說法,這種方式叫做構造函數注入:
//小明類
public class XiaoMing implements IReader{
private IRead read;
//構造函數注入
public XiaoMing(IRead read){
this.read = read;
}
//閱讀
public void read(){
read.read();
}
}
2)Setter方法傳遞依賴對象
在類中通過Setter方法聲明依賴關系,依照依賴注入的說法,這是Setter依賴注入
//小明類
public class XiaoMing implements IReader{
private IRead read;
//Setter依賴注入
public setRead(IRead read){
this.read = read;
}
//閱讀
public void read(){
read.read();
}
}
3)接口聲明依賴
在接口的方法中聲明依賴對象,在為什么我們要符合依賴倒置原則的例子中,我們采用了接口聲明依賴的方式,該方法也叫做接口注入。
4、依賴倒置原則的經驗
依賴倒置原則的本質就是通過抽象(接口或抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊間的松耦合。我們在項目中使用這個原則要遵循下面的規(guī)則:
1)每個類盡量都有接口或者抽象類,或者抽象類和接口兩都具備
2)變量的表面類型盡量是接口或者抽象類
3)任何類都不應該從具體類派生
4)盡量不要覆寫基類的方法
如果基類是一個抽象類,而這個方法已經實現了,子類盡量不要覆寫。類間依賴的是抽象,覆寫了抽象方法,對依賴的穩(wěn)定性會有一定的影響。
5)結合里氏替換原則使用
依賴倒置原則是6個設計原則中最難以實現的原則,它是實現開閉原則的重要方法,在項目中,大家只要記住是”面向接口編程”就基本上是抓住了依賴倒置原則的核心了。
五、迪米特原則
這個原則在開發(fā)中還是非常有用的。
1、定義
大致意思是:即一個類應該盡量不要知道其他類太多的東西,不要和陌生的類有太多接觸。
迪米特原則還有一個解釋:Only talk to your immediate friends(只與直接朋友通信)。
什么叫直接朋友呢?每個對象都必然會與其他對象有耦合關系,兩個對象之間的耦合就成為朋友關系,這種關系類型有很多,例如:組合,聚合,依賴等。朋友類也可以這樣定義:出現在成員
變量,方法的輸入輸出參數中的類,稱為朋友類。
2、案例說明
上體育課,我們經常有這樣一個場景:
體育老師上課前要體育委員確認一下全班女生到了多少位,也就是體育委員清點女生的人數。如圖:

分析:這里其實體育老師和體育委員是朋友,因為他們是有業(yè)務來源,而女生人數是和體育委員有業(yè)務來源(它們是朋友),但是體育老師和女生人數是沒有直接業(yè)務來源的所以體育老師類中
不應該參雜女生相關信息,這就是迪米特原則
(1)沒有才有迪米特原則
體育老師類
public class Teacher{
//老師對體育委員發(fā)一個命令,讓其清點女生人數的方法
public void command(GroupLeader groupLeader){
List<Girl> listGirls = new ArrayList();
//初始化女生,發(fā)現老師和女生有耦合
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
//告訴體育委員開始清點女生人數
groupLeader.countGirls(listGirls);
}
}
體育委員類
public class GroupLeader{
//清點女生數量
public void countGirls(List<Girl> listGirls){
System.out.println("女生人數是:"+listGirls.size());
}
}
女生類
publci class Girl{
}
測試類
public class Client{
public static void main(Strings[] args){
Teacher teacher = new Teacher();
//老師給體育委員發(fā)清點女生人數的命令
teacher.command(new GroupLeader());
}
}
分析:我們再回頭看Teacher類,Teacher類只有一個朋友類GroupLeader,Girl類不是朋友類,但是Teacher與Girl類通信了,這就破壞了Teacher類的健壯性,Teacher類的方法竟然與一個不是
自己的朋友類Girl類通信,這是不允許的,嚴重違反了迪米特原則。
(2)采用迪米特原則
我們對程序進行如下修改,將類圖修改如下:

修改后的老師類:(注意這里面已經沒有女生信息了)
public class Teacher{
//老師對體育委員發(fā)一個命令,讓其清點女生人數
public void command(GroupLeader groupLeader){
//告訴體育委員開始清點女生人數
groupLeader.countGirls();
}
}
修改后的體育委員類
public class GroupLeader{
private List<Girl> listGirls;
public GroupLeader(List<Girl> listGirls){
this.listGirls = listGirls;
}
//清點女生數量
public void countGirls(){
System.out.println("女生人數是:"+listGirls.size());
}
}
修改后的測試類
public class Client{
public static void main(Strings[] args){
//產生女生群體
List<Girl> listGirls = new ArrayList<Girl>();
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
Teacher teacher = new Teacher();
//老師給體育委員發(fā)清點女生人數的命令
teacher.command(new GroupLeader(listGirls));
}
}
對程序修改,把Teacher中對Girl群體的初始化移動到場景類中,同時在GroupLeader中增加對Girl的注入,避開了Teacher類對陌生類Girl的訪問,降低了系統(tǒng)間的耦合,提高了系統(tǒng)的健壯性。
在實踐中經常出現這樣一個方法,放在本類中也可以,放到其它類中也可以。那怎么處理呢?你可以堅持一個原則:如果一個方法放在本類中,即不增加類間關系,也對本類不產生負面影響,那就放到本類中。
迪米特原則的核心觀念就是類間解耦,弱耦合,只有弱耦合后,類的復用率才可以提高。其結果就是產生了大量的中轉或跳轉類,導致系統(tǒng)復雜,為維護帶來了難度。所以,我們在實踐時要反
復權衡,即要讓結構清晰,又做到高內聚低耦合。
3、自己理解
迪米特原則在自己開發(fā)中一定要培養(yǎng)這種思想,因為它沒有那么模糊,而且這個原則沒啥爭議。
六、開閉原則
這個原則更像是前五個原則的總綱,前五個原則就是圍著它轉的,只要我們盡量的遵守前五個原則,那么設計出來的系統(tǒng)應該就比較符合開閉原則了,相反,如果你違背了太多,那么你的系統(tǒng)或許也不太遵循開閉原則。
1、定義
一句話,對修改關閉,對擴展開放。
就是說我任何的改變都不需要修改原有的代碼,而只需要加入一些新的實現,就可以達到我的目的,這是系統(tǒng)設計的理想境界,但是沒有任何一個系統(tǒng)可以做到這一點,哪怕我一直最欣賞的
spring框架也做不到,雖說它的擴展性已經強到變態(tài)。這個就不說了,字面上也能理解個八九分,它對我來講太抽象。雖然它很重要。
總結
如果你理解會運用了這六大原則,那么你寫出的代碼一定是非常漂亮的,二不是那么臃腫,遍地第都是垃圾代碼了。
以上就是詳解java設計模式之六大原則的詳細內容,更多關于java設計模式之六大原則的資料請關注腳本之家其它相關文章!
相關文章
java通過HttpServletRequest獲取post請求中的body內容的方法
本篇文章主要介紹了java通過HttpServletRequest獲取post請求中的body內容的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02

