最近看了看程傑
前輩的《大話設計模式》
,主要是上次面試被問設計模式,答的很菜。。。經過一段時間的沉澱,現在來談談我對其單例模式
的理解。
一、單例模式
1、定義
單例模式
(Singleton),保證一個類僅有一個實例
,並且提供全局訪問該實例
的接口。簡單的說,既要保證該類只有一個實例
,也要保證全局訪問的就是這個唯一的實例
。
2、使用場景
那麼肯定有小夥伴會問,單例模式
有啥使用場景嗎?限制一個類只有一個實例
,這種場景比較少吧。。。
確實,限制一個類只有一個實例
的場景比較少,但是並不代表單例模式
毫無用武之地。舉個栗子,運用程序是如何維持某個窗口只一個的?比如系統桌面右鍵菜單。
如果不控制窗口對應的對象同一時刻只有一個,那麼可能你每點一次,都會彈出一個同樣的窗口。這顯然不符合實際邏輯,也會造成資源的浪費。
其實還有另外一種普遍的場景,類中的靜態屬性
。我想寫過面向對象編程序思想的程序小夥伴都在類中定義過static
屬性,我們把某個變量加上static
關鍵字修飾,目的是把這個變量聲明爲類
本身所有,而不是類的實例
所有。最主要的作用不就是類的所有實例
都來訪問這同一個static
屬性,這正好與我們單例模式的思想吻合。
二、舉例(靜態屬性)
上面提到了 類中的靜態屬性
是單例模式思想的一種特殊運用,下面我們通過代碼來模擬一下。
public class SingleTon {
// 對object屬性添加static修飾(這個變量屬於SingleTon類,不屬於實例)
public static MyClass object = new MyClass();
public static void main(String[] args) {
// 新建兩個SingleTon對象
SingleTon singleTonOne = new SingleTon();
SingleTon singleTonTwo = new SingleTon();
// 由於Java提供通過類實例訪問類靜態屬性的途徑,所以這裏我們測試一下
// 兩個實例訪問的是否爲同一個實例object(這裏只是演示,實際編寫代碼千萬別通過對象訪問類靜態屬性!!!)
System.out.println(singleTonOne.object);
System.out.println(singleTonTwo.object);
}
}
class MyClass{
public MyClass(){
}
}
由於在Java
語言中,每個類只能加載一次,因此SingleTon
類中的object
屬性只會被初始化一次,也就是隻有一個。通過觀察程序的輸出,確實發現兩個實例共用SingleTon
類中的object
靜態屬性,做到了只有一個實例。但這個程序不能稱爲嚴格的單例
,因爲在任何地方都可以通過調用MyClass
類的構造器來手動創建MyClass
的實例,因此這提示我們需要修改單例對應的類的構造器訪問權限爲私有(private關鍵字修飾)。在閱讀下文前,可以思考一下如何改造這個程序,使MyClass
變爲嚴格的單例。
三、兩種實現方式(Java)
單例模式
的實現,常見的方法有兩種,分別是懶漢式
、餓漢式
,兩者都將類的構造器私有化,但是各有優缺點。
1、餓漢式
將構造器私有化
,並且用static屬性
初始化一個實例。這樣每次就只能訪問同一個實例,並且限制了類的new操作(構造器被私有化了)。
public class SingleTonTest {
public static void main(String[] args) {
// 通過SingleTon.getInstance接口,兩次獲取SingleTon類的實例
SingleTon singleTonOne = SingleTon.getInstance();
SingleTon singleTonTwo = SingleTon.getInstance();
// 打印兩次獲取的實例的內存地址
System.out.println(singleTonOne);
System.out.println(singleTonTwo);
}
}
class SingleTon{
// SingleTon靜態屬性,初始化時new一個對象(類中能使用該類聲明的private方法)
private static SingleTon singleTon = new SingleTon();
// 構造器私有了,外部不能new對象(保證了虛擬機中只能有一個實例)
private SingleTon(){
}
// 對外展示訪問SingleTon實例的接口(保證了全局訪問的都是SingleTon類的同一個實例)
public static SingleTon getInstance() {
return singleTon;
}
}
該方式的特點就是在加載
類的時候就new一個唯一的實例
,以後直接通過相應的API獲取,繼而保證了實例唯一,並且全局訪問同一個實例。提高了使用效率,但是可能出現資源浪費的情況,假設整個程序運行過程中都沒訪問這個實例,那不是白白生成了一個實例。。。
2、懶漢式
同樣將類的構造器私有化
,但是對外展示的獲取實例的API智能生成實例,如果已經生成過了實例,直接放回之前的示例,否則生成一個新的實例。
public class SingleTonTest {
public static void main(String[] args) {
// 通過SingleTon.getInstance接口,兩次獲取SingleTon類的實例
SingleTon singleTonOne = SingleTon.getInstance();
SingleTon singleTonTwo = SingleTon.getInstance();
// 打印兩次獲取的實例的內存地址
System.out.println(singleTonOne);
System.out.println(singleTonTwo);
}
}
class SingleTon{
// SingleTon靜態屬性,指向生成的SingleTon實例
private static SingleTon singleTon = null;
// 構造器私有了,外部不能new對象(保證了虛擬機中只能有一個實例)
private SingleTon(){
}
// 對外展示訪問SingleTon實例的接口(保證了全局訪問的都是SingleTon類的同一個實例)
// 可能存在線程不安全的情況(多個線程同時調用getInstance,此時可能生成多個實例),使用synchronized關鍵字修飾,默認是SingleTon.class鎖
public static synchronized SingleTon getInstance() {
if (singleTon == null) {
// 爲null就new一個實例
singleTon = new SingleTon();
}
return singleTon;
}
}
懶漢式
中的懶
字,就是由 先判斷是否生成過實例,再考慮是否生成實例而來。這種方式解決了餓漢式可能浪費資源的問題,但是降低了使用效率,因爲每次都需要加鎖、判斷是否爲null、釋放鎖。
四、總結
單例模式
是衆多設計模式中較爲簡單的一種,核心思想是保證某類有且只用一個實例對象,並且需要保證全局都只能訪問這個唯一的實例。
經典的例子就是GUI中的窗口,同一個窗口類一般只能有一個對象。不過博主個人認爲,類中的static屬性是單例思想的一種特殊運用,因爲它保證了在本類中所用的對象訪問同一個static屬性
(大家看看就行,別當真😂😂😂)。
資料參考:
《大話設計模式》程傑著 單例模式