Nội dung
Singleton là một mẫu thiết kế khởi tạo, đảm bảo rằng chỉ có một đối tượng cùng kiểu tồn tại và cung cấp một điểm truy cập duy nhất vào nó cho bất kỳ mã nào khác.
Singleton có những ưu và nhược điểm gần giống như các biến toàn cục. Mặc dù chúng siêu tiện dụng, nhưng chúng phá vỡ tính mô đun của mã của bạn.
Cách sử dụng mẫu
Sử dụng: Rất nhiều nhà phát triển coi mẫu Singleton là một mẫu phản thiết kế mẫu. Đó là lý do tại sao việc sử dụng nó đang giảm trong mã C++.
Nhận biết: Singleton có thể được nhận dạng bằng một phương thức khởi tạo tĩnh, phương thức này trả về cùng một đối tượng được lưu trong bộ nhớ cache.
Singleton thơ ngây
Khá dễ dàng để triển khai một Singleton thơ ngây. Bạn chỉ cần ẩn hàm tạo và thực hiện một phương thức tạo tĩnh.
Việc này có thể không chính xác trong môi trường đa luồng. Nhiều luồng có thể gọi phương thức khởi tạo đồng thời và nhận một số thể hiện của lớp Singleton.
main.cpp
/**
* Lớp Singleton định nghĩa phương thức GetInstance dùng như một phương thức thay
* thế cho hàm tạo và cho phép các client truy cập lặp đi lặp lại cùng một thể hiện
* của lớp này.
*/
class Singleton
{
/**
* Phương thức khởi tạo của Singleton phải luôn ở private để ngăn các lệnh gọi
* xây dựng trực tiếp với toán tử new.
*/
protected:
Singleton(const std::string value): value_(value)
{
}
static Singleton* singleton_;
std::string value_;
public:
/**
* Singleton không được sao chép.
*/
Singleton(Singleton &other) = delete;
/**
* Singleton không thể được gán.
*/
void operator=(const Singleton &) = delete;
/**
* Đây là phương thức tĩnh kiểm soát quyền truy cập vào cá thể singleton.
* Trong lần chạy đầu tiên, nó tạo một đối tượng singleton và đặt nó vào
* trường tĩnh. Trong các lần chạy tiếp theo, nó trả về đối tượng hiện
* có của client được lưu trữ trong trường tĩnh.
*/
static Singleton *GetInstance(const std::string& value);
/**
* Cuối cùng, bất kỳ singleton nào cũng phải xác định một số logic,
* có thể được thực thi trên thể hiện của nó.
*/
void SomeBusinessLogic()
{
// ...
}
std::string value() const{
return value_;
}
};
Singleton* Singleton::singleton_= nullptr;;
/**
* Các phương thức tĩnh nên được định nghĩa bên ngoài lớp.
*/
Singleton *Singleton::GetInstance(const std::string& value)
{
/**
* Đây là cách an toàn hơn để tạo một thể hiện. instance = new Singleton
* rất nguy hiểm trong trường hợp có hai luồng muốn truy cập cùng lúc
*/
if(singleton_==nullptr){
singleton_ = new Singleton(value);
}
return singleton_;
}
void ThreadFoo(){
// Mã sau mô phỏng quá trình khởi tạo.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}
void ThreadBar(){
// Mã sau mô phỏng quá trình khởi tạo.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}
int main()
{
std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
"If you see different values, then 2 singletons were created (booo!!)\n\n" <<
"RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();
return 0;
}
Kết quả
If you see the same value, then singleton was reused (yay!
If you see different values, then 2 singletons were created (booo!!)
RESULT:
BAR
FOO
Singleton trong môi trường đa luồng
Để khắc phục sự cố phải đồng bộ hóa các luồng trong lần tạo đối tượng Singleton đầu tiên.
main.cpp
/**
* Lớp Singleton định nghĩa phương thức GetInstance dùng như một phương thức thay
* thế cho hàm tạo và cho phép các client truy cập lặp đi lặp lại cùng một thể hiện
* của lớp này.
*/
class Singleton
{
/**
* Phương thức khởi tạo / hủy của Singleton phải luôn ở private để ngăn các lệnh gọi
* xây dựng / hủy trực tiếp với toán tử new.
*/
private:
static Singleton * pinstance_;
static std::mutex mutex_;
protected:
Singleton(const std::string value): value_(value)
{
}
~Singleton() {}
std::string value_;
public:
/**
* Singleton không được sao chép.
*/
Singleton(Singleton &other) = delete;
/**
* Singleton không thể được gán.
*/
void operator=(const Singleton &) = delete;
/**
* Đây là phương thức tĩnh kiểm soát quyền truy cập vào cá thể singleton.
* Trong lần chạy đầu tiên, nó tạo một đối tượng singleton và đặt nó vào
* trường tĩnh. Trong các lần chạy tiếp theo, nó trả về đối tượng hiện
* có của client được lưu trữ trong trường tĩnh.
*/
static Singleton *GetInstance(const std::string& value);
/**
* Cuối cùng, bất kỳ singleton nào cũng phải xác định một số logic,
* có thể được thực thi trên thể hiện của nó.
*/
void SomeBusinessLogic()
{
// ...
}
std::string value() const{
return value_;
}
};
/**
* Các phương thức tĩnh nên được định nghĩa bên ngoài lớp.
*/
Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;
/**
* Lần đầu gọi GetInstance, chúng ta sẽ khóa vị trí lưu trữ
* và sau đó chúng ta đảm bảo một lần nữa rằng biến là null
* và sau đó chúng ta set giá trị.
*/
Singleton *Singleton::GetInstance(const std::string& value)
{
if (pinstance_ == nullptr)
{
std::lock_guard<std::mutex> lock(mutex_);
if (pinstance_ == nullptr)
{
pinstance_ = new Singleton(value);
}
}
return pinstance_;
}
void ThreadFoo(){
// Mã sau mô phỏng quá trình khởi tạo.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}
void ThreadBar(){
// Mã sau mô phỏng quá trình khởi tạo.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}
int main()
{
std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
"If you see different values, then 2 singletons were created (booo!!)\n\n" <<
"RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();
return 0;
}
Kết quả
If you see the same value, then singleton was reused (yay!
If you see different values, then 2 singletons were created (booo!!)
RESULT:
FOO
FOO
Để lại một bình luận