面向?qū)ο蟮牧笤瓌t
單一職責原則 Single Responsibility Principle
一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)、數(shù)據(jù)的封裝。兩個完全不一樣的功能就不應(yīng)該放在一個類中。開閉原則 Open Close Principle
軟件中的對象(類、模塊、函數(shù)等)應(yīng)該對于擴展是開放的,但是對于修改是封閉的。
在軟件的生命周期內(nèi),因為變化、升級、維護等原因需要對軟件原有的代碼進行修改時,可能會將錯誤引入原本已經(jīng)經(jīng)過測試的舊代碼中,破壞原有系統(tǒng)。因此當軟件需要變化時,我們應(yīng)該盡量通過擴展的方式來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)。盡量,說明OCP原則并不是說絕對不能修改原始類的,當原始類不好的時候應(yīng)該盡早的進行重構(gòu)。里氏替換原則 Liskov Substitution Principle
所有引用基類的地方,必須能透明地使用其子類的對象。里氏替換原則就是依賴于繼承和多態(tài)這兩大特性。
開閉原則和里氏替換原則往往是相互依賴的,通過里氏替換來達到對擴展開放,對修改 封閉的效果。依賴倒置原則 Dependence Inversion Principle
一種特定的解耦形式,使得高層次的模塊不依賴底層次的模塊。具體而言即:1)高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;2)抽象不應(yīng)該依賴細節(jié);3)細節(jié)應(yīng)該依賴抽象。
在Java語言中,一般抽象是指接口或者抽象類,兩者都無法被實例化。細節(jié)是指實現(xiàn)類,實現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細節(jié)。
模塊間的依賴通過抽象發(fā)生,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的。接口隔離原則 Interface Segregation Principle
客戶端不應(yīng)該依賴它不需要的接口。目的是系統(tǒng)解耦,從而更容易重構(gòu)、更改和重新部署。
上述是面向?qū)ο缶幊痰?個基本原則。當這些原則被一起應(yīng)用時,它們使得一個軟件系統(tǒng)更清晰、簡單,最大程度地擁抱變化。迪米特原則 最少知識原則 Least Knowledge Principle
一個類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少,類的內(nèi)部如何實現(xiàn)與調(diào)用者或者依賴者沒關(guān)系,調(diào)用者或者依賴者只需要知道它需要的方法即可,其他的可一概不管。
單例模式是應(yīng)用最廣的模式之一。
定義
確保某個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。
以上可知單例類的特點:
- 只能有一個實例
- 必須自己創(chuàng)建自己的唯一實例
- 必須給其他對象(整個系統(tǒng))提供這一實例
使用場景
當這個類的對象在多個地方創(chuàng)建的時候,使得內(nèi)部的方法多次調(diào)用,但是我們希望只要一個對象操作這個方法,或者不希望多個地方同時調(diào)用這個方法,我們需要保持這個方法的單一性質(zhì),我們就用單例模式。
比如:訪問IO和數(shù)據(jù)庫資源、單一彈框等。
實現(xiàn)單例模式的關(guān)鍵點
- 構(gòu)造函數(shù)不對外開放,一般為Private
- 通過一個靜態(tài)方法或者枚舉返回單例類對象
- 確保單例類的對象有且僅有一個,尤其是在多線程環(huán)境下
- 確保單例類對象在反序列化時不會重新構(gòu)建對象

單例模式的實現(xiàn)方法
1.懶漢模式
//懶漢式單例類.在第一次調(diào)用的時候?qū)嵗约?public class LHS_Singleton {
private static LHS_Singleton instance;
private LHS_Singleton() {
}
//靜態(tài)工廠方法
public static synchronized LHS_Singleton getInstance() {
if (instance == null) {
instance = new LHS_Singleton();
}
return instance;
}
}
在getInstance()方法中用到了synchronized同步鎖,保證了線程安全。
懶漢式是典型的時間換空間
- 優(yōu)點:單例只有在使用的時候才會被實例化,一定程度上節(jié)約了資源。
- 缺點:第一次加載的時候需要及時進行實例化,最大問題在于每次調(diào)用都要進行同步,會造成不必要的同步開銷,降低整個訪問速度,且每次都需要進行判斷是否需要創(chuàng)建實例。
2.DCL雙重檢查加鎖模式
public class DCL_Singleton {
private volatile static DCL_Singleton singleton = null;
private DCL_Singleton() {
}
public static DCL_Singleton getSingleton() {
//先檢查實例是否存在,如果不存在才進入下面的同步塊
if (singleton == null) {
//同步塊,線程安全的創(chuàng)建實例
synchronized (DCL_Singleton.class) {
//再次檢查實例是否存在,如果不存在才真正的創(chuàng)建實例
if (singleton == null) {
singleton = new DCL_Singleton();
}
}
}
return singleton;
}
}
與懶漢式不同在于,在getInstance()方法中不用再進行同步鎖。第一個判斷singleton == null主要是為了避免不必要的同步,第二次判斷則是為了singleton = null的時候在線程安全的情況下創(chuàng)建實例。
-
優(yōu)點:資源利用率高,既能夠在需要的時候才初始化單例,又能保證線程安全,且單例對象初始化后調(diào)用
getInstance()不再進行同步鎖。 - 缺點:第一次加載反應(yīng)稍慢,由于Java內(nèi)存模型原因偶爾會失敗,高并發(fā)情況下有一定缺陷。
- 注:由于
volatile關(guān)鍵字可能會屏蔽掉虛擬機中一些必要的代碼優(yōu)化,所以運行效率并不是很高。因此雖然可以使用“雙重檢查加鎖”機制來實現(xiàn)線程安全的單例,但并不建議大量采用,可以根據(jù)情況來選用。
3.靜態(tài)內(nèi)部類模式
public class JTNBL_Singleton {
private JTNBL_Singleton() {
}
public static JTNBL_Singleton getInstanse() {
return SingletonHolder.instance;
}
/**
* 類級的內(nèi)部類,也就是靜態(tài)的成員式內(nèi)部類,該內(nèi)部類的實例與外部類的實例
* 沒有綁定關(guān)系,而且只有被調(diào)用到時才會裝載,從而實現(xiàn)了延遲加載。
*/
private static class SingletonHolder {
/**
* 靜態(tài)初始化器,由JVM來保證線程安全
*/
private static final JTNBL_Singleton instance = new JTNBL_Singleton();
}
}
當getInstance()方法第一次被調(diào)用的時候,它第一次讀取SingletonHolder.instance,會使SingletonHolder類得到初始化;而這個類在裝載并被初始化的時候,會初始化它的靜態(tài)域,從而創(chuàng)建JTNBL_Singleton的實例,由于是靜態(tài)的域,因此只會在虛擬機加載類的時候初始化一次,并由虛擬機來保證它的線程安全性。
優(yōu)點:這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化。
注
在上述的幾種實現(xiàn)單例模式的情況中,有一個情況下他們會出現(xiàn)重新創(chuàng)建對象的情況,那就是反序列化。如果要杜絕單例對象在被反序列化時重新生成對象,那么必須加入下面的方法:
private Object readResolve() throws ObjectStreamException {
return instance;
}
4.枚舉單例
對于枚舉,則不存在反序列化問題,因為即使反序列化枚舉單例也不會重新生成新的實例。
public enum MJ_Singleton {
/**
* 定義一個枚舉的元素,它就代表了MJ_Singleton的一個實例。
*/
INSTANCE;
/**
* 單例可以有自己的操作
*/
public void doSomething() {
System.out.println("Hello World");
}
}
枚舉在JAVA中與普通的類一樣,不僅能有字段,還能有自己的方法,最重要的是默認枚舉實例的創(chuàng)建是線程安全的,并且在任何情況下它都是一個單例。
按照《高效Java 第二版》中的說法:單元素的枚舉類型已經(jīng)成為實現(xiàn)Singleton的最佳方法。用枚舉來實現(xiàn)單例非常簡單,只需要編寫一個包含單個元素的枚舉類型即可。
5.使用容器實現(xiàn)單例模式
public class RQ_Singleton {
private static Map<String, Object> objectMap = new HashMap<>();
private RQ_Singleton() {
}
public static void registerService(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}
在程序初始,將多種單例類型注入到一個統(tǒng)一的單例管理類中,通過key來獲取對應(yīng)類型的對象。
這種方式使得我們可以管理多種類型的單例,并且在使用時可以通過統(tǒng)一的接口進行獲取操作,降低了耦合度,對用戶隱藏了具體實現(xiàn)。
6.餓漢模式
public class EHS_Singleton {
// 在這個類被加載的時候,靜態(tài)變量single會被初始化,
// 此時類的私有構(gòu)造子會被調(diào)用。這時單例類的唯一實例就被構(gòu)造出來了。
private static EHS_Singleton instance = new EHS_Singleton();
private EHS_Singleton() {
}
public static EHS_Singleton getInstance() {
return instance;
}
}
餓漢式其實是一種比較形象的稱謂。既然餓,那么在創(chuàng)建對象實例的時候就比較著急,餓了嘛,于是在裝載類的時候就創(chuàng)建對象實例。
餓漢式是典型的空間換取時間,當類裝載時就會創(chuàng)建類的實例,不管你用不用,先創(chuàng)建出來,然后每次調(diào)用的時候,就不需要再判斷,節(jié)省了運行時間。
單例模式的優(yōu)點
- 在內(nèi)存中只有一個實例,減少了內(nèi)存開支,特別是對于一個對象需要頻繁地創(chuàng)建、銷毀時,而且創(chuàng)建或銷毀時性能又無法優(yōu)化,單例模式的優(yōu)勢便非常明顯。
- 當一個對象的產(chǎn)生需要比較多的資源時,如讀取配置、產(chǎn)生其他依賴對象時,則可以通過在應(yīng)用啟動時直接產(chǎn)生一個單例對象,然后永久駐留內(nèi)存的方式來解決。
- 單例模式可以避免對資源的多重占用,例如寫文件操作,避免了對同一個資源文件的同時寫操作。
- 單例模式可以在系統(tǒng)設(shè)置全局的訪問點,優(yōu)化和共享資源訪問,例如可以設(shè)計一個單例類,負責所有數(shù)據(jù)表的映射處理。
單例模式的缺點
- 單例模式一般沒有接口,擴展很困難,若要擴展,除了修改代碼基本上沒有第二種途徑可實現(xiàn)
- 單例對象如果持有Context,那么很容易引發(fā)內(nèi)存泄漏,此時需要注意傳遞給單例對象的Context最好是Application Context
引用:
Android 源碼設(shè)計模式解析與實戰(zhàn)》
Java設(shè)計模式之單例模式及在Android中的重要使用