"A class of which only a single instance can exist"
The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. It is named after the singleton set, which is defined to be a set containing one element. The office of the President of the United States is a Singleton. The United States Constitution specifies the means by which a president is elected, limits the term of office, and defines the order of succession. As a result, there can be at most one active president at any given time. Regardless of the personal identity of the active president, the title, "The President of the United States" is a global point of access that identifies the person in the office.
Rules of thumb
- Abstract Factory, Builder, and Prototype can use Singleton in their implementation.
- Facade objects are often Singletons because only one Facade object is required.
- State objects are often Singletons.
- The advantage of Singleton over global variables is that you are absolutely sure of the number of instances when you use Singleton, and you can change your mind and manage any number of instances.
- The Singleton design pattern is one of the most inappropriately used patterns. Singletons are intended to be used when a class must have exactly one instance, no more, no less. Designers frequently use Singletons in a misguided attempt to replace global variables. A Singleton is, for intents and purposes, a global variable. The Singleton does not do away with the global; it merely renames it.
- When is Singleton unnecessary? Short answer: most of the time. Long answer: when it's simpler to pass an object resource as a reference to the objects that need it, rather than letting objects access the resource globally. The real problem with Singletons is that they give you such a good excuse not to think carefully about the appropriate visibility of an object. Finding the right balance of exposure and protection for an object is critical for maintaining flexibility.
- Our group had a bad habit of using global data, so I did a study group on Singleton. The next thing I know Singletons appeared everywhere and none of the problems related to global data went away. The answer to the global data question is not, "Make it a Singleton." The answer is, "Why in the hell are you using global data?" Changing the name doesn't change the problem. In fact, it may make it worse because it gives you the opportunity to say, "Well I'm not doing that, I'm doing this" – even though this and that are the same thing.
Là một nhà tư vấn được trả lương hậu hĩnh tại MegaGigaco, bạn phải xử lý các sự cố về hiệu năng hệ thống. “Hệ thống hình như ngày càng chậm chạp hơn.” Các lập trình viên nói:
“Hmm,” bạn nói, “Tôi lưu ý các bạn rằng chúng ta đang có một đối tượng dữ liệu có kích thước khá lớn, khoảng 20Mb”
“Vâng”, họ nói.
“Cùng một thời điểm, các bạn sử dụng bao nhiêu đối tượng này?”
“Khoảng 219”, các lập trình viên nói
“Trời, vậy các bạn sử dụng 219 đối tượng 20Mb trong lúc chương trình hoạt động?” Bạn nói. “Chẳng lẽ không ai thấy được vấn đề ở đây à?”
“Không”, họ đồng thanh nói.
Bạn nói với họ “Các bạn sử dụng quá nhiều tài nguyên hệ thống. Các bạn có hàng trăm đối tượng to lớn mà máy tính phải xử lý. Các bạn có thật sự cần tất cả chúng?”
“Vâng…” họ nói.
“Tôi nghĩ là không,” bạn nói. “Tôi sẽ sửa chữa vấn đề này bằng cách sử dụng mẫu duy nhất Singleton.”
Chương này nói về việc kiểm soát số lượng đối tượng mà bạn phải tạo ra trong mã nguồn của mình. Có hai mẫu thiết kế đặc biệt giúp ích cho bạn: mẫu duy nhất Singleton và mẫu “hạng ruồi” flyweight.
Với mẫu duy nhất Singleton, bạn luôn đảm bảo rằng chỉ có duy nhất một đối tượng cho một lớp cụ thể trong suốt ứng dụng. Với mẫu “hạng ruồi” flyweight, bạn cũng có duy nhất một đối tượng cho một lớp, nhưng khi nhìn vào mã của bạn, ta có thể thấy giống như đang có nhiều đối tượng vậy. Đây là một thủ thuậ t khéo léo.
Tạo một đối tượng duy nhất với mẫu duy nhất Singleton.
Tôi bắt đầu với mẫu Singleton và xử lý rắc rối mà lập trình viên MegaGigaCo gặp phải. Họ muốn chắn chắc rằng chỉ tạo duy nhất một đối tượng cho một lớp cụ thể mặc cho người khác có cố gắng tạo bao nhiêu đối tượng đi nữa.
Các lập trình viên đang tạo ra hàng trăm đối tượng Database trong mã nguồn, và rắc rối là từng đối tượng này có kích thước rất lớn. Đâu là giải pháp? Mẫu duy nhất Singeton là câu trả lời.
Mẫu duy nhất Singleton chắc chắn rằng bạn có thể khởi tạo chỉ duy nhất một đối tượng cho một lớp. Nếu bạn không sử dụng mẫu thiết kế này, toán tử new như thường sử dụng, sẽ tạo ra liên tiếp nhiều đối tượng mới như sau:
Ghi nhớ: Để chắc chắn rằng bạn chỉ có duy nhất một đối tượng, mặc cho người khác có hiện thực bao nhiêu phiên bản đi nữa, hãy sử dụng mẫu duy nhất Singleton. Cuốn sách GoF nói rằng, mẫu Singleton “Đảm bảo rằng một lớp chỉ có duy nhất một thể hiện và cung cấp một biến toàn cục để truy cập nó”
Bạn sử dụng mẫu Singleton khi bạn muốn hạn chế việc sử dụng tài nguyên (thay vì việc tạo không hạn chế số lượng đối tượng) hoặc khi bạn cần phải xử lý một đối tượng nhạy cảm, mà dữ liệu của nó không thể chia sẻ cho mọi thể hiện, như registry của Windows chẳng hạn.
Gợi ý: Ngoài đối tượng bản ghi registry, bạn có thể sử dụng mẫu Singleton khi bạn muốn hạn chế số lượng các thể hiện được tạo bởi vì bạn muốn chia sẻ dữ liệu của các đối tượng này. Ví dụ như khi bạn có một đối tượng cửa sổ window hay hộp thoại dialog, cần phải hiện thị và thay đổi dữ liệu, bạn sẽ không muốn tạo nhiều thể hiện của đối tượng này, vì bạn sẽ bị bối rối trong việc phải truy cập dữ liệu của thể hiện nào.
Việc tạo một đối tượng duy nhất cũng rất quan trọng khi bạn sử dụng đa luồng và khi bạn không muốn sự đụng độ dữ liệu xảy ra. Ví dụ bạn đang làm việc với một đối tượng cơ sở dữ liệu, và các thể hiện khác cũng làm việc trên cùng cơ sở dữ liệu đó, việc đụng độ có thể gây ra các vấn đề nghiêm trọng. Tôi sẽ thảo luận cách làm việc với mẫu Singleton và đa luồng trong chương này.
Bất cứ khi nào bạn thật sự cần duy nhất một thể hiện của một lớp, hãy nghĩ tới mẫu Singleton ( thay vì dùng toán tử new).
Tạo lớp cơ sở dữ liệu Database dựa trên kiểu Singleton
Giờ là lúc bạn bắt tay vào viết mã nguồn. Bạn sẽ tạo một lớp tên Database mà các lập trình viên trong công ty sẽ sử dụng. Lớp này có một hàm khởi dựng đơn giản, như mã sau:public class cDatabase
{
private int _record;
private string _name;
public cDatabase(string n)
{
_name = n;
_record = 0;
}
}
Bạn cần phải thêm vào hai hàm editRecord, cho phép bạn chỉnh sửa một bản ghi, và hàm getName, trả về tên gọi của Database.
public class cDatabase
{
private int _record;
private string _name;
public cDatabase(string n)
{
_name = n;
_record = 0;
}
public void editRecord(string operation)
{
Console.Writeln("Performing a: " + operation +
" opration on record: " + _record + " in a database: " + _name);
}
public string getName()
{
return _name;
}
}
Tới giờ mọi việc vẫn tốt đẹp. Bất cứ khi nào bạn tạo một đối tượng bằng toán tử new, một đối tượng mới sẽ được tạo ra. Nếu bạn tạo 3 database, bạn sẽ có 3 đối tượng như sau:
cDatabase dataone = new cDatabase("products");
cDatabase dataone = new cDatabase("products Also");
cDatabase dataone = new cDatabase("products Again");
Làm sao để bạn có thể tránh việc tạo một đối tượng mới khi sử dụng toán tử new? Đây là một giải pháp – làm cho hàm khởi dụng từ toàn cục (public) trở thành cục bộ (private)
private cDatabase(string n)
{
_name = n;
_record = 0;
}
Điều này ngăn cản mọi người sử dụng hàm khởi dựng, ngoài trừ chính trong lớp này gọi tới. Nhưng đợi một chút, có gì không ổn ở đây? Ai ở trên trái đất này lại cần có một hàm khởi dựng riêng tư vậy? Làm sao bạn có thể tạo một đối tượng khi bạn không thể gọi hàm khởi tạo nó?
Bạn đã làm cho hàm khởi dựng trở nên riêng tư và cách duy nhất để phần còn lại của thế giới khởi tạo đối tượng đó là thêm vào một hàm tạo đối tượng, và gọi nó khi bạn chắn chắn muốn tạo một đối tượng duy nhất cho lớp này.
Hãy xem đoạn mã sau:
public class cDatabase
{
private int _record;
private string _name;
private cDatabase(string n)
{
_name = n;
_record = 0;
}
}
OK. Đầu tiên bạn ngăn chặn việc khởi tạo bằng toán tử new. Và bây giờ cách duy nhất là tạo một hàm để gọi việc khởi tạo đối tượng, thông thường hàm này có tên getInstance (hay createInstance hoặc một cái tên cụ thể như createDatabase cũng được).
Chú ý rằng hàm này được gán phạm vi công cộng và là một phương thức tĩnh để bạn có thể truy cập tới nó thông qua tên lớp (ví dụ như Database.getInstance()) (ND: public và static là hai khái niệm trong OOP. Public giúp hàm có thể được sử dụng từ bất kì lớp khác, static giúp ta có thể sử dụng hàm trực tiếp từ tên lớp, không cần thông qua một đối tượng lớp. )
public class cDatabase
{
private int _record;
private string _name;
private cDatabase(string n)
{
_name = n;
_record = 0;
}
public static cDatabase getInstance(string n)
{
}
}
Hàm này sẽ trả về một đối tượng Database, nhưng hàm chỉ hoạt động khi có ít nhất một đối tượng đã tồn tại. Vì thế đầu tiên ta cần kiểm tra đối tượng này, tôi gọi nó là singleObject, xem nó đã tồn tại chưa? Nếu chưa, tôi sẽ tạo nó. Và sau đó trả giá trị nó về cho hàm.
public class cDatabase
{
private static cDatabase _singleObj;
private int _record;
private string _name;
private cDatabase(string n)
{
_name = n;
_record = 0;
}
public static cDatabase getInstance(string n)
{
if (_singleObj == null)
_singleObj = new cDatabase(n);
return _singleObj;
}
}
Vấn đề đã được giải quyết. Bây giờ chỉ có duy nhất một đối tượng Database tồn tại trong cùng một thời điểm. (Vấn đề đa luồng ta sẽ giải quyết trong phần sau của chương). Việc gọi hàm getInstance sẽ cho ta một đối tượng như hình sau:
Khi bạn gọi getInstance lần nữa, bạn sẽ nhận được cùng một đối tượng như lần đầu.
Không quan tâm đến việc bạn gọi bao nhiêu lần getInstance, bạn luôn nhận được cùng một đối tượng. Đó chính là cách bạn phải làm với mẫu singleton.
Chạy thử ví dụ với mẫu Singleton
Bắt đầu bằng việc tạo một đối tượng Database với tên là products, sau đó gọi hàm getName:public static void main()
{
cDatabase database;
database = cDatabase.getInstance("products");
Console.Writeline("This is the: " + cDatabase.getName() + " database");
}
Sau đó bạn tiếp tục tạo một đối tượng Database với tên là employees, và gọi lại hàm getName để kiểm tra:
public static void main()
{
cDatabase database;
database = cDatabase.getInstance("products")
Console.Writeline("This is the " + database.getName() + " database");
database = cDatabase.getInstace("employees");
Console.Writeline("This is the " + database.getName() + " database");
}
Tuy nhiên đối tượng Database đã được tạo, vì vậy trong lần thứ hai, hàm getInstance vẫn trả về đối tượng Database cũ, và kết quả là bạn nhận được thông báo:
This is the products database
This is the products database
Quá rõ ràng. Bạn đã nhận được duy nhất một đối tượng cho dù đã thực hiện việc tạo hai lần. Cách thức bạn làm việc như sau: ngăn cản việc khởi tạo bằng toán tử new, và tạo một hàm mới để tạo đối tượng theo ý bạn. Đó chính là cách mẫu Singleton hoạt động.
Đối với vấn đề đa luồng!
Hãy xem lại hàm getInstance trong ví dụ trên:
Có một lỗ hổng tiềm tàng ở đây, tuy nhỏ nhưng là một lỗ hổng rõ ràng, đó là khi làm việc với đa luồng. Hãy nhớ rằng, bạn muốn đảm bảo rằng chỉ có duy nhất một đối tượng Database tồn tại. Nhưng khi bạn có nhiều luồng chương trình chạy cùng lúc, bạn sẽ gặp rắc rối. Cụ thể là, hãy chú ý đoạn mã kiểm tra sự tồn tại của đối tượng Database:
Nếu có hai luồng cùng thực hiện hàm kiểm tra này một lúc, hai luồng này đều thỏa điều kiện của hàm if ( tức chưa có đối tượng nào được tạo), và điều này có nghĩa là cả hai luồng đều tạo ra một đối tượng Database.
Làm sao để chỉnh sửa chỗ này? xem đoạn mã sau:
private static object _obj = new object();
public static cCustomer createInstance(string name)
{
// double check to avoid multiThreading
if (_singleObj == null)
{
lock (_obj)
{
if (_singleObj == null)
_singleObj = new cCustomer(name);
}
}
return _singleObj;
}
sử dụng lock để khóa việc truy cập vào hàm getInstance, trong khi hàm getInstance được chạy. Bất cứ luồng nào muốn gọi hàm getInstance, đều phải đợi hàm này hoạt động xong. và kỹ thuật này đã giải quyết được vấn đề đa luồng.
Kể từ khi bạn sử dụng kỹ thuật đồng bộ hóa trên hàm getInstance, bạn không còn lo lắng về vấn đề đa luồng nữa. Chỉ duy nhất một luồng được gọi hàm getInstance. Nó ngăn chặn việc tạo đối tượng bằng một bức tường an toàn, việc kiểm tra ở trên cho thấy, nếu đối tượng muốn tạo đã tồn tại, hàm sẽ không tạo, ngược lại, sẽ tạo đối tượng cho lớp.
Ref:
https://sourcemaking.com/design_patterns/singleton
https://haihth.wordpress.com/2013/02/23/dp-chapter5/
0 comments:
Post a Comment