一.單一職責原則(SRP)
就一個類而言,應該僅有一個引起它變化的原因。一個類中應該是一組相關性很高的函數、數據的封裝
通俗的講就是,一個類或一個方法中只做一件事,實現一個功能。有時單一職責的界定可能並不清晰,我的做法通常是將類和方法的註釋寫詳細,這樣一目瞭然地就知道是不是幹了過多的事,並且別人看你的代碼也能快速瞭解功能。
比如你寫了一個ImageLoder類,主體功能是去調用加載網絡圖片,實現完後你發現裏面既有從網絡下載圖片的代碼,又有緩存的代碼,這個時候就可以考慮將下載圖片和進行緩存的部分抽離出來單獨成類,主ImageLoader裏僅保持接口調用即可。
二.開閉原則(OCP)
軟件中的對象(類、模塊、函數等)應該對於擴展是開放的,對於修改是封閉的
需求是不斷變化的,如果我們每次應對變化時都需要修改原先的類或方法,就可能會導致原本穩定的系統變的不穩定。我們只應該在原有的系統本來有錯誤的時候進行改正,在產生新的變化時只進行拓展,當然這同時要求你的既有系統設計時就要考慮的全面一些,否則如何能愉快的擴展呢?所以基礎架構就顯得格外重要。
舉個工作中的小例子,我們可以通過繼承很好的進行拓展。像我們的項目中,有個專門處理頭像的自定義View HeadImageView ,後來美術要求在一些位置的頭像需要加個白色描邊,這時我並沒有直接對 HeadImageView 進行修改,而是新建了一個類 StrokeHeadImageView 重寫onDraw 方法 drawCircle 加了一層描邊。
三.里氏替換原則(LSP)
所有引用基類的地方必須能透明地使用其子類的對象
通俗的講就是要善於利用抽象與繼承,當我們接到某個功能,同時它可能有多種實現方式時,我們可以將核心方法進行一層接口抽象,在調用處使用抽象父類,具體使用哪一個實現子類都對整個系統框架不會產生影響。
工作中一個很好的例子就是圖片加載框架,一般我們都會使用一個第三方庫 Glide、Picasso、Fresco等,如果我們直接使用的話,這時項目需要,要進行框架的替換,你會發現這是一項浩大的工程,但是如果我們早有預見進行了一層統一的封裝,你會發現這只是替換一下一個具體的加載引擎實現類而已。其實這也是策略模式的重要思想。
Github上有類似思想的封裝:https://github.com/ladingwu/ImageLoaderFramework 可以參考
四.依賴倒置原則(DIP)
高層模塊不應該依賴低層模塊,兩者都應該依賴抽象,抽象不應該依賴細節,細節應該依賴抽象
總結就是面向接口編程,模塊間的依賴通過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是通過接口或抽象類產生的。
還是單一職責裏那個例子,我們的ImageLoader 裏不應該依賴於具體的某種緩存,具體的圖片下載實現,這些都應該被抽象爲接口,ImageLoader中只做接口間的打交道,你們怎麼做的我不管,甚至ImageLoader就應該定義爲接口,我們具體實現一個ImageLoader然後將各個功能的實現再進行拼裝組合。
五.接口隔離原則(ISP)
類間的依賴關係應該建立在最小的接口上
接口隔離將龐大、臃腫的接口拆分成更小更具體的接口,使系統解耦合,層次更清晰
比如我們在對流進行操作時,完畢後需要將流關閉,就會產生這樣的代碼:
try{
...
}catch(){
...
}finally{
if(null != stream){
try{
stream.close();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
雖然只是流關閉接口的調用,但卻多出來一坨很垃圾的嵌套代碼,使得程序可讀性,類體結構變大,這時我們可以寫一個工具類來統一處理這種實現 Closeable 接口的對象的關閉
public final class CloseUtils{
private CloseUtils(){}
public static void closeQuietly(Closeable closeable){
if(null != closeable){
try{
closeable.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
}
再比如我們平時對ViewPager進行監聽處理時,都會addOnPageChangeListener,然後實現三個回調方法,但是我們通常是隻使用其中的一個,無故的導致代碼變多,這時我們可以實現一個默認實現三個方法的Adapter類,再使用時 addOnPageChangeListener(傳入我們的Adapter)並且只實現我們需要的方法就好了
public class OnPageChangeListenerAdapter implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
同樣的,系統的AnimatorListenerAdapter 也是同樣的思想,雖然動畫監聽的接口有很多,但我們不是每個都要使用。
六.迪米特原則(LOD)
一個對象應該對其他對象有最少的瞭解
簡單來講就是我們只與直接必須依賴的對象進行通信,並且最好是僅調用最簡單的接口方法,不要在接口方法中摻雜沒有直接關係的對象。
比如我們實現磁盤緩存時,只需要使用 DiskImageCahce.cache方法,具體怎麼緩存的,使用DiskLruCache 還是什麼都不需要知道,屏蔽了細節。
工作中常用的設計模式
1.單例模式
這應該是我們最常用的模式了,可以很方便的存儲和訪問一些數據,比如我們項目中AccountManager 就是使用單例存儲了一些用戶數據便於在應用活動期間進行數據訪問。
但是這個模式有幾個弊端需要注意:
<1>容易造成內存泄漏,所以要避免持有一些長生命週期的類。
<2>要區分清單例和靜態類靜態方法的區別,避免濫用。
<3>在Android 中,應用退到後臺,時間長了應用可能會被殺掉,這時再從最近任務棧中將應用拉到前臺時,系統會幫我們恢復一些Activity相關的東西,但是單例存的內容就沒有了,所以一些數據還是要做持久化處理。
2.策略模式
就如同我上面講的,在對一些第三方庫進行一層抽取時格外好用。
還有一些例如 工廠方法模式,建造者模式,觀察者模式等都是平時工作或自己寫個庫時比較常用的模式,就不一一贅述了。
總結
面向對象的設計模式就是基於三大特性(抽象、繼承、多態)和六大基本原則,在一代代軟件開發的過程中凝結的智慧,我們應該在深入理解這些特性和原則後,靈活的使用這些模式而不是生搬硬套過度設計