面向?qū)ο蟮牧笤瓌t
單一職責(zé)原則 Single Responsibility Principle
一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)俱饿、數(shù)據(jù)的封裝寥殖。兩個完全不一樣的功能就不應(yīng)該放在一個類中。開閉原則 Open Close Principle
軟件中的對象(類账胧、模塊窝剖、函數(shù)等)應(yīng)該對于擴(kuò)展是開放的棠隐,但是對于修改是封閉的。
在軟件的生命周期內(nèi)轻黑,因?yàn)樽兓簟⑸墶⒕S護(hù)等原因需要對軟件原有的代碼進(jìn)行修改時苔悦,可能會將錯誤引入原本已經(jīng)經(jīng)過測試的舊代碼中轩褐,破壞原有系統(tǒng)。因此當(dāng)軟件需要變化時玖详,我們應(yīng)該盡量通過擴(kuò)展的方式來實(shí)現(xiàn)變化把介,而不是通過修改已有的代碼來實(shí)現(xiàn)勤讽。盡量,說明OCP原則并不是說絕對不能修改原始類的拗踢,當(dāng)原始類不好的時候應(yīng)該盡早的進(jìn)行重構(gòu)脚牍。里氏替換原則 Liskov Substitution Principle
所有引用基類的地方,必須能透明地使用其子類的對象巢墅。里氏替換原則就是依賴于繼承和多態(tài)這兩大特性诸狭。
開閉原則和里氏替換原則往往是相互依賴的,通過里氏替換來達(dá)到對擴(kuò)展開放君纫,對修改 封閉的效果驯遇。依賴倒置原則 Dependence Inversion Principle
一種特定的解耦形式,使得高層次的模塊不依賴底層次的模塊蓄髓。具體而言即:1)高層模塊不應(yīng)該依賴低層模塊叉庐,兩者都應(yīng)該依賴其抽象;2)抽象不應(yīng)該依賴細(xì)節(jié)会喝;3)細(xì)節(jié)應(yīng)該依賴抽象陡叠。
在Java語言中,一般抽象是指接口或者抽象類肢执,兩者都無法被實(shí)例化枉阵。細(xì)節(jié)是指實(shí)現(xiàn)類,實(shí)現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié)预茄。
模塊間的依賴通過抽象發(fā)生兴溜,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的反璃。接口隔離原則 Interface Segregation Principle
客戶端不應(yīng)該依賴它不需要的接口昵慌。目的是系統(tǒng)解耦,從而更容易重構(gòu)淮蜈、更改和重新部署斋攀。
上述是面向?qū)ο缶幊痰?個基本原則。當(dāng)這些原則被一起應(yīng)用時梧田,它們使得一個軟件系統(tǒng)更清晰淳蔼、簡單,最大程度地?fù)肀ё兓?/p>迪米特原則 最少知識原則 Least Knowledge Principle
一個類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少裁眯,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒關(guān)系鹉梨,調(diào)用者或者依賴者只需要知道它需要的方法即可,其他的可一概不管穿稳。
單例模式是應(yīng)用最廣的模式之一存皂。
定義
確保某個類只有一個實(shí)例,而且自行實(shí)例化并向整個系統(tǒng)提供這個實(shí)例。
以上可知單例類的特點(diǎn):
- 只能有一個實(shí)例
- 必須自己創(chuàng)建自己的唯一實(shí)例
- 必須給其他對象(整個系統(tǒng))提供這一實(shí)例
使用場景
當(dāng)這個類的對象在多個地方創(chuàng)建的時候旦袋,使得內(nèi)部的方法多次調(diào)用骤菠,但是我們希望只要一個對象操作這個方法,或者不希望多個地方同時調(diào)用這個方法疤孕,我們需要保持這個方法的單一性質(zhì)商乎,我們就用單例模式。
比如:訪問IO和數(shù)據(jù)庫資源祭阀、單一彈框等鹉戚。
實(shí)現(xiàn)單例模式的關(guān)鍵點(diǎn)
- 構(gòu)造函數(shù)不對外開放,一般為Private
- 通過一個靜態(tài)方法或者枚舉返回單例類對象
- 確保單例類的對象有且僅有一個专控,尤其是在多線程環(huán)境下
- 確保單例類對象在反序列化時不會重新構(gòu)建對象
單例模式的實(shí)現(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)點(diǎn):單例只有在使用的時候才會被實(shí)例化伦腐,一定程度上節(jié)約了資源却桶。
- 缺點(diǎn):第一次加載的時候需要及時進(jìn)行實(shí)例化,最大問題在于每次調(diào)用都要進(jìn)行同步蔗牡,會造成不必要的同步開銷,降低整個訪問速度嗅剖,且每次都需要進(jìn)行判斷是否需要創(chuàng)建實(shí)例辩越。
2.DCL雙重檢查加鎖模式
public class DCL_Singleton {
private volatile static DCL_Singleton singleton = null;
private DCL_Singleton() {
}
public static DCL_Singleton getSingleton() {
//先檢查實(shí)例是否存在,如果不存在才進(jìn)入下面的同步塊
if (singleton == null) {
//同步塊信粮,線程安全的創(chuàng)建實(shí)例
synchronized (DCL_Singleton.class) {
//再次檢查實(shí)例是否存在黔攒,如果不存在才真正的創(chuàng)建實(shí)例
if (singleton == null) {
singleton = new DCL_Singleton();
}
}
}
return singleton;
}
}
與懶漢式不同在于,在getInstance()
方法中不用再進(jìn)行同步鎖强缘。第一個判斷singleton == null
主要是為了避免不必要的同步督惰,第二次判斷則是為了singleton = null
的時候在線程安全的情況下創(chuàng)建實(shí)例。
-
優(yōu)點(diǎn):資源利用率高旅掂,既能夠在需要的時候才初始化單例赏胚,又能保證線程安全,且單例對象初始化后調(diào)用
getInstance()
不再進(jìn)行同步鎖商虐。 - 缺點(diǎn):第一次加載反應(yīng)稍慢觉阅,由于Java內(nèi)存模型原因偶爾會失敗,高并發(fā)情況下有一定缺陷秘车。
- 注:由于
volatile
關(guān)鍵字可能會屏蔽掉虛擬機(jī)中一些必要的代碼優(yōu)化典勇,所以運(yùn)行效率并不是很高。因此雖然可以使用“雙重檢查加鎖”機(jī)制來實(shí)現(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)部類的實(shí)例與外部類的實(shí)例
* 沒有綁定關(guān)系般码,而且只有被調(diào)用到時才會裝載,從而實(shí)現(xiàn)了延遲加載谈火。
*/
private static class SingletonHolder {
/**
* 靜態(tài)初始化器侈询,由JVM來保證線程安全
*/
private static final JTNBL_Singleton instance = new JTNBL_Singleton();
}
}
當(dāng)getInstance()
方法第一次被調(diào)用的時候,它第一次讀取SingletonHolder.instance
糯耍,會使SingletonHolder
類得到初始化扔字;而這個類在裝載并被初始化的時候,會初始化它的靜態(tài)域温技,從而創(chuàng)建JTNBL_Singleton
的實(shí)例革为,由于是靜態(tài)的域,因此只會在虛擬機(jī)加載類的時候初始化一次舵鳞,并由虛擬機(jī)來保證它的線程安全性震檩。
優(yōu)點(diǎn):這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性蜓堕,同時也延遲了單例的實(shí)例化抛虏。
注
在上述的幾種實(shí)現(xiàn)單例模式的情況中,有一個情況下他們會出現(xiàn)重新創(chuàng)建對象的情況套才,那就是反序列化迂猴。如果要杜絕單例對象在被反序列化時重新生成對象,那么必須加入下面的方法:
private Object readResolve() throws ObjectStreamException {
return instance;
}
4.枚舉單例
對于枚舉背伴,則不存在反序列化問題沸毁,因?yàn)榧词狗葱蛄谢杜e單例也不會重新生成新的實(shí)例。
public enum MJ_Singleton {
/**
* 定義一個枚舉的元素傻寂,它就代表了MJ_Singleton的一個實(shí)例息尺。
*/
INSTANCE;
/**
* 單例可以有自己的操作
*/
public void doSomething() {
System.out.println("Hello World");
}
}
枚舉在JAVA中與普通的類一樣,不僅能有字段疾掰,還能有自己的方法搂誉,最重要的是默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下它都是一個單例静檬。
按照《高效Java 第二版》中的說法:單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法勒葱。用枚舉來實(shí)現(xiàn)單例非常簡單,只需要編寫一個包含單個元素的枚舉類型即可巴柿。
5.使用容器實(shí)現(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)一的接口進(jìn)行獲取操作,降低了耦合度,對用戶隱藏了具體實(shí)現(xiàn)至非。
6.餓漢模式
public class EHS_Singleton {
// 在這個類被加載的時候钠署,靜態(tài)變量single會被初始化,
// 此時類的私有構(gòu)造子會被調(diào)用荒椭。這時單例類的唯一實(shí)例就被構(gòu)造出來了谐鼎。
private static EHS_Singleton instance = new EHS_Singleton();
private EHS_Singleton() {
}
public static EHS_Singleton getInstance() {
return instance;
}
}
餓漢式其實(shí)是一種比較形象的稱謂。既然餓趣惠,那么在創(chuàng)建對象實(shí)例的時候就比較著急狸棍,餓了嘛,于是在裝載類的時候就創(chuàng)建對象實(shí)例味悄。
餓漢式是典型的空間換取時間草戈,當(dāng)類裝載時就會創(chuàng)建類的實(shí)例,不管你用不用侍瑟,先創(chuàng)建出來唐片,然后每次調(diào)用的時候,就不需要再判斷涨颜,節(jié)省了運(yùn)行時間费韭。
單例模式的優(yōu)點(diǎn)
- 在內(nèi)存中只有一個實(shí)例,減少了內(nèi)存開支庭瑰,特別是對于一個對象需要頻繁地創(chuàng)建揽思、銷毀時,而且創(chuàng)建或銷毀時性能又無法優(yōu)化见擦,單例模式的優(yōu)勢便非常明顯。
- 當(dāng)一個對象的產(chǎn)生需要比較多的資源時羹令,如讀取配置鲤屡、產(chǎn)生其他依賴對象時,則可以通過在應(yīng)用啟動時直接產(chǎn)生一個單例對象福侈,然后永久駐留內(nèi)存的方式來解決酒来。
- 單例模式可以避免對資源的多重占用,例如寫文件操作肪凛,避免了對同一個資源文件的同時寫操作堰汉。
- 單例模式可以在系統(tǒng)設(shè)置全局的訪問點(diǎn),優(yōu)化和共享資源訪問伟墙,例如可以設(shè)計(jì)一個單例類翘鸭,負(fù)責(zé)所有數(shù)據(jù)表的映射處理。
單例模式的缺點(diǎn)
- 單例模式一般沒有接口戳葵,擴(kuò)展很困難就乓,若要擴(kuò)展,除了修改代碼基本上沒有第二種途徑可實(shí)現(xiàn)
- 單例對象如果持有Context,那么很容易引發(fā)內(nèi)存泄漏生蚁,此時需要注意傳遞給單例對象的Context最好是Application Context
引用:
Android 源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
Java設(shè)計(jì)模式之單例模式及在Android中的重要使用