定義
單例是一種創(chuàng)建型設(shè)計模式,讓你能夠保證一個類只有一個實例,并提供一個訪問該實例的全局節(jié)點。
前言
1. 問題
單例模式同時解決了兩個問題,所以違反了單一職責(zé)原則:
- 保證一個類只有一個實例。
- 為該實例提供一個全局訪問節(jié)點。
為什么會有人想要控制一個類所擁有的實例數(shù)量?最常見的原因是控制某些共享資源(例如數(shù)據(jù)庫或文件)的訪問權(quán)限。它的運(yùn)作方式是這樣的:如果你創(chuàng)建了一個對象,同時過一會兒后你決定再創(chuàng)建一個新對象,此時你會獲得之前已創(chuàng)建的對象,而不是一個新對象。
注意,普通構(gòu)造函數(shù)無法實現(xiàn)上述行為,因為構(gòu)造函數(shù)的設(shè)計決定了它必須總是返回一個新對象。
還記得你用過的那些存儲重要對象的全局變量嗎?它們在使用上十分方便,但同時也非常不安全,因為任何代碼都有可能覆蓋掉那些變量的內(nèi)容,從而引發(fā)程序崩潰。和全局變量一樣,單例模式也允許在程序的任何地方訪問特定對象。但是它可以保護(hù)該實例不被其他代碼覆蓋。
還有一點:你不會希望解決同一個問題的代碼分散在程序各處的。因此更好的方式是將其放在同一個類中,特別是當(dāng)其他代碼已經(jīng)依賴這個類時更應(yīng)該如此。
如今,單例模式已經(jīng)變得非常流行,以至于人們會將只解決上文描述中任意一個問題的東西稱為單例。
結(jié)構(gòu)
?
單例(Singleton) 類聲明了一個名為getInstance 獲取實例的靜態(tài)方法來返回其所屬類的一個相同實例。
單例的構(gòu)造函數(shù)必須對客戶端(Client) 代碼隱藏。調(diào)用獲取實例方法必須是獲取單例對象的唯一方式。
適用場景
- 如果程序中的某個類對于所有客戶端只有一個可用的實例,可以使用單例模式。
單例模式禁止通過除特殊構(gòu)建方法以外的任何方式來創(chuàng)建自身類的對象。該方法可以創(chuàng)建一個新對象,但如果該對象已經(jīng)被創(chuàng)建,則返回已有的對象。
- 如果你需要更加嚴(yán)格地控制全局變量,可以使用單例模式。
單例模式與全局變量不同,它保證類只存在一個實例。除了單例類自己以外,無法通過任何方式替換緩存的實例。請注意, 你可以隨時調(diào)整限制并設(shè)定生成單例實例的數(shù)量,只需修改獲取實例方法, 即getInstance 中的代碼即可實現(xiàn)。
實現(xiàn)方式
- 在類中添加一個私有靜態(tài)成員變量用于保存單例實例。
- 聲明一個公有靜態(tài)構(gòu)建方法用于獲取單例實例。
- 在靜態(tài)方法中實現(xiàn)"延遲初始化"。該方法會在首次被調(diào)用時創(chuàng)建一個新對象,并將其存儲在靜態(tài)成員變量中。此后該方法每次被調(diào)用時都返回該實例。
- 將類的構(gòu)造函數(shù)設(shè)為私有。類的靜態(tài)方法仍能調(diào)用構(gòu)造函數(shù),但是其他對象不能調(diào)用。
- 檢查客戶端代碼,將對單例的構(gòu)造函數(shù)的調(diào)用替換為對其靜態(tài)構(gòu)建方法的調(diào)用。
優(yōu)點
- 你可以保證一個類只有一個實例。
- 你獲得了一個指向該實例的全局訪問節(jié)點。
- 僅在首次請求單例對象時對其進(jìn)行初始化。
缺點
- 違反了單一職責(zé)原則。該模式同時解決了兩個問題。
- 單例模式可能掩蓋不良設(shè)計,比如程序各組件之間相互了解過多等。
- 該模式在多線程環(huán)境下需要進(jìn)行特殊處理,避免多個線程多次創(chuàng)建單例對象。
- 單例的客戶端代碼單元測試可能會比較困難,因為許多測試框架以基于繼承的方式創(chuàng)建模擬對象。由于單例類的構(gòu)造函數(shù)是私有的,而且絕大部分語言無法重寫靜態(tài)方法,所以你需要想出仔細(xì)考慮模擬單例的方法。要么干脆不編寫測試代碼,或者不使用單例模式。
懶漢單例模式代碼
1. 線程不安全的懶漢單例模式
Singleton.h:
- 構(gòu)造函數(shù)私有:即單例模式只能在內(nèi)部私有化
- 實例對象static:保證全局只有一個
- 外界通過
GetInstance()
獲取實例對象
#ifndef SINGLETON_H_
#define SINGLETON_H_
#include <iostream>
#include <string>
class Singleton {
public:
static Singleton* GetInstance() {
if (instance_ == nullptr) {
instance_ = new Singleton();
}
return instance_;
}
private:
Singleton() {}
static Singleton* instance_;
};
#endif // SINGLETON_H_
Singleton.cpp:?
#include "Singleton.h"
// 靜態(tài)變量instance初始化不要放在頭文件中, 如果多個文件包含singleton.h會出現(xiàn)重復(fù)定義問題
Singleton* Singleton::instance_ = nullptr;
?main.cpp:
#include <iostream>
#include "Singleton.h"
int main() {
Singleton *s1 = Singleton::GetInstance();
Singleton *s2 = Singleton::GetInstance();
std::cout << "s1地址: " << s1 << std::endl;
std::cout << "s2地址: " << s2 << std::endl;
return 0;
}
線程安全
上述代碼并不是線程安全的,當(dāng)多個線程同時調(diào)用Singleton::GetInstance()
,可能會創(chuàng)建多個實例從而導(dǎo)致內(nèi)存泄漏(會new多次但我們只能管理唯一的一個instance_
),我們這里簡單通過互斥鎖實現(xiàn)線程安全。
Singleton.hpp
#ifndef SINGLETON_H_
#define SINGLETON_H_
#include <iostream>
#include <string>
#include <mutex>
class Singleton {
public:
static Singleton* GetInstance() {
if (instance_ == nullptr) {
// 加鎖保證多個線程并發(fā)調(diào)用getInstance()時只會創(chuàng)建一個實例
m_mutex_.lock();
if (instance_ == nullptr) {
instance_ = new Singleton();
}
m_mutex_.unlock();
}
return instance_;
}
private:
Singleton() {}
static Singleton* instance_;
static std::mutex m_mutex_;
};
#endif // SINGLETON_H_
?Singleton.cpp:
#include "Singleton.h"
// 靜態(tài)變量instance初始化不要放在頭文件中, 如果多個文件包含singleton.h會出現(xiàn)重復(fù)定義問題
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::m_mutex_;
main.cpp:?
#include <iostream>
#include "Singleton.h"
int main() {
Singleton *s1 = Singleton::GetInstance();
Singleton *s2 = Singleton::GetInstance();
std::cout << "s1地址: " << s1 << std::endl;
std::cout << "s2地址: " << s2 << std::endl;
return 0;
}
餓漢單例模式代碼
Singleton.h:
#ifndef SINGLETON_H_
#define SINGLETON_H_
class Singleton {
public:
static Singleton* GetInstance() {
return instance_;
}
private:
Singleton() {}
static Singleton* instance_;
};
#endif // SINGLETON_H_
Singleton.cpp:文章來源:http://www.zghlxwxcb.cn/news/detail-500591.html
#include "Singleton.h"
Singleton* Singleton::instance_ = new Singleton();
main.cpp:?文章來源地址http://www.zghlxwxcb.cn/news/detail-500591.html
#include <iostream>
#include "Singleton.h"
int main() {
Singleton *s1 = Singleton::GetInstance();
Singleton *s2 = Singleton::GetInstance();
std::cout << "s1地址: " << s1 << std::endl;
std::cout << "s2地址: " << s2 << std::endl;
return 0;
}
到了這里,關(guān)于單例模式(Singleton)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!