總結:本篇文字分為兩個部分。第一部分:設計模式基本常識;第二部分:軟件設計中的六大原則,並詳細分析了單一職責原則。(本篇文章的時間軸參考:為知筆記支撐文件夾\Java設計模式(時間序列圖).vsdx)
部分一:初識設計模式
什么是設計模式?James拿到這個論點時,很是迷惑!
模式?是不是一個模子?模式識別——計算機領域的經典問題?
設計模擬?軟件的設計模式?不懂!!!
但是在實際編碼、調試過程中,James的遇到過很是難解的問題:工程代碼中有過多的冗余代碼——代碼復用性不高;需求一旦改變,需要更改很多地方的代碼邏輯——代碼靈活性不強……
那先看看設計模式的概念吧!
設計模式是一套被反復使用、為多數人知曉、經過分類編目的、代碼設計經驗的總結。為了編寫可重用性代碼,讓代碼更容易被他人理解,並保證代碼可靠性而使用的設計思想。
設計模式使代碼編制真正工程化。
設計模式是軟件行業智慧積累的結晶;它提出了一系列標准術語,概括了相關行業中經驗豐富的從業者所應用的所有概念和方法。
常用的23種設計模式如下:
1.單例模式;
2.工廠方法模式;
3.抽象工廠模式;
4.模版方法模式;
5.建造者模式;
6.代理模式;
7.原型模式;
8.中介者模式;
9.命令模式;
10.責任鏈模式;
11.裝飾模式;
12.策略模式;
13.適配器模式;
14.迭代器模式;
15.組合模式;
16.觀察者模式;
17.門面模式;
18.備忘錄模式;
19.訪問者模式;
20.狀態模式;
21.解釋器模式;
22.享元模式;
23.橋梁模式;
設計模式的起源是面向對象程序設計思想,是面向對象設計的精髓——抽象;面向對象通過類和對象來實現抽象,實現時產生了面向對象的三種重要機制:封裝、繼承和多態。而這三種機制衍生了各式各樣的設計模式。
在運用面向對象思想進行軟件設計時,需要遵循以下原則:
1. 單一責任原則;
2. 里氏替換原則;
3. 依賴倒置原則;
4. 接口隔離原則;
5. 迪米特法原則;
6. 開閉原則;
這23種設計模式按設計意圖可組織成五類:接口型模式,責任型模式、構造型模式,操作型模式以及擴展型模式。模式的設計意圖指出了應用一個模式的價值所在。但上面所說的23中的某種設計模式,並不是僅僅支持一種設計意圖。
在軟件設計過程中,只要我們盡量遵循以上設計原則,設計出來的軟件一定是優秀的,且足夠健壯、穩定,並有足夠的靈活性來迎接需求的變更。
那James還是不知道為什么,比如:這些原則的含義是什么?它們為什么而出現,解決了什么工程問題?這些原則和之前講述的23種設計模式有什么聯系?如何在我們的工程代碼中使用……
這些疑問都需要James一一解決,不過James相信自己肯定能夠戰勝這些困難。(縱使是住着握手樓、吃着方便面、擠着公交車,只要能夠活下來,就有希望!別人能做出來的東西,自己為什么不行?要有必勝的信心!)
部分二:從設計原則開始——單一職責原則
單一職責原則:應該有且僅有一個原因引起類的變更。
為什么在這里出現的是類?是不是僅僅只應用於類的設計?
(為什么會出現這種設計原則?)當一個類承擔了過多的職責,就等於把這些職責耦合在一起,一個職責的變化可能會削弱或者一直這個類完成其他職責的能力。問題就出在耦合性上!
軟件設計真正要做的工作是:發現職責並把那些職責互相分離。單一職責可使類的復雜性降低,其實現的工作有明確且清晰的定義。
那問題來了,James不明白什么是“類的變更”?
(如何實踐單一職責原則?)考慮到如何使用代碼實現單一責任原則(代碼即是思路嘛),好在有百度和谷歌。下面就使用網站上的例子講解,James認為:用這個例子比較容易說明問題。
需求分析:烹飪,做一道魚香茄子。
public interface Cooking { // 獲取菜名 String getCookingName(); void setCookingName(String cookingName); // 烹飪之前的准備工作:摘菜、洗菜等等,准備相應的食材 void doPrepare(String vegetableName); // 烹飪模式:蒸、煮、炒、炸等,並提供菜名 void doFire(String cookingName, String mode); }
如果James想要烹飪一道魚香茄子,可以覆寫以上四個方法,從獲知菜名、准備食材到具體的烹飪操作。從信息和行為的角度來說,前面兩個方法是魚香茄子相關的信息,后兩個則是相關的行為(烹飪具體的行為)。
如果考慮如下方式是否更完善?
public interface ICookingInfo { // 獲取菜名 String getCookingName(); void setCookingName(String cookingName); }
public interface ICookingFire { // 烹飪之前的准備工作:摘菜、洗菜等等,准備相應的食材 void doPrepare(ICookingInfo info); // 烹飪模式:蒸、煮、炒、炸等,並提供菜名 void doFire(ICookingInfo info, String mode); }
Cooking類包含兩個部分,一個部分的職責是獲取菜名及相關信息(口味、特點…),另一個部分的職責是具體烹飪(當然需要菜名相關信息啦)。
如果ICookingInfo發生改變,勢必會造成ICookingFire的改變。對於職責不同的兩個部分,需要拆分。那在Cooking類中,只需要實現上述兩個不同接口,即可實現烹飪一道菜的功能。
public class Cooking implements ICookingInfo, ICookingFire { @Override public String getCookingName() { return null; } @Override public void setCookingName(String cookingName) { } @Override public void doPrepare(ICookingInfo info) { } @Override public void doFire(ICookingInfo info, String mode) { } }
產生了Cooking對象后,可將其視為ICookingInfo或者ICookingFire接口使用;如果需要設置菜名或其他信息,可以當做是ICookingInfo的實現類;要是進行烹飪操作,就當做是ICookingFire的實現類。(突然引出了一個問題:James貌似不太理解類和其實現的接口之間的關系……)
總之,在接口設計中,如果接口可細分為不同的“職責”,就可將該接口進一步細分。
上述僅僅只是從接口角度解讀了:單一職責原則,此外還可以用在類的設計、方法的定義上。歸結為一點:自己的事情自己做,並承擔相應的責任。
可參考一下實例分析:
1. http://blog.csdn.net/zhengzhb/article/details/7278174 從類的角度解讀;
2. 待續……
單一職責原則的優點:
1. 類的復雜性降低,實現什么職責都有清晰明確的定義;
2. 可讀性提高;
3. 可維護性提高;
4. 變更引起的風險降低;如果接口的單一職責做得好,一個接口修改只對相應的實現類有影響,對其他的接口無影響,對系統的擴展性、維護性都有非常大的幫助
單一職責原則提出了一個編寫程序的標准,用“職責”或“變化原因”來衡量接口或類設計得是否優良,但是“職責”和“變化原因”都是不可度量的,因項目而異,因環境而異。
單一職責原則可以使用到哪些方面?單一職責原則適用於接口、類,同樣適用於方法(一個方法盡可能只做一件事情)。接口一定要做到單一職責,類的設計盡量做到只有一個原因引起變化。
但凡事都有需要權衡利弊的地方,不能因為生搬硬套單一職責原則,而讓系統變得繁雜而龐大。