Tiếp cận lại về static và singleton

Xuất phát từ mấy vấn đề nên muốn cover lại kiến thức về static và singleton.

– Một là để hiểu rõ thêm vấn đề
– Hai là làm rõ thêm vấn đề

Vậy static có đặc điểm gì? Khi nào nên dùng nó?
Singleton thì sao? Dùng thế nào? Các vấn đề cần lưu ý khi sử dụng?

Ok, xắn ✋ vào tìm hiểu static trước.

Định nghĩa: một class static sẽ không cho bạn tạo một cài đặt. Nghĩa là bạn sẽ không thể sử dụng từ khoá new để tạo object. Muốn sử dụng methods và properties cần khai báo thêm từ khoá static và gọi trực tiếp bằng tên lớp.

Đặc điểm
– Có vùng nhớ riêng, không bị thay đổi.
– Chỉ có một refrence duy nhất trong ứng dụng.
– Do không thể tạo instantiated, nên không cần cấp phát thêm vùng nhớ.
– Life cycle giống application.
– Khởi tạo một lần duy nhất khi chạy chương trình.

Nên dùng khi nào
– Xây dựng các lớp không phụ thuộc vào object. Ví dụ class Utility, lưu các thông tin dùng chung cho toàn bộ ứng dụng.
– Tiết kiệm bộ nhớ.

Ok, giờ tới singleton.

Mình xin được copy lại góc nhìn của anh Hiền blog guru
Vì sao ra đời?
Trong GoF, Singleton được đưa ra với mục đích sau: “Ensure a class only has one instance, and provide a global point of access to it.” – “Đảm bảo một class chỉ có duy nhất một instance, và cung cấp một điểm truy cập duy nhất trên toàn cục tới instance.”

Lợi ích:
Nó cung cấp ý tưởng của việc một instance duy nhất, điều mà chúng ta gặp trong nhiều bài toán thực tế: một ứng dụng được khởi tạo, một cấu hình hệ thống, một logger…

Đặc điểm:
Về mặt runtime
– Tiết kiệm: Không giống như static trong class, object và các giá trị trong đó chỉ được khởi tạo khi cần thiết. Memory và cả CPU đều được tiết kiệm (static khởi tạo ngay khi chạy chương trình).
– Chủ động quản lý: Cho phép chủ động quản lý life cycle, giải phóng khi cần thiết.
Về mặt design
– Có khả năng thừa kế.
– Có thể kết hợp với các partern khác.
- Abstract hơn sử dụng static trong class.

Bàn thêm về singleton

Tư tưởng: giải quyết 2 bài toán khác nhau (dù có vẻ liên quan): “Ensure a class only has one instance, and provide a global point of access to it.”
– Một class có duy nhất một instance;
– Cung cấp một điểm truy cập duy nhất trên toàn cục tới instance.
– Tác giả đã vô tình kèm cả lời giải trong bài toán với giả định: để có một điểm truy cập toàn cục duy nhất thì chỉ có duy nhất một instance được tạo ra từ một class. Bài toán “một điểm truy cập” có thể được giải quyết bởi Facade, Wrapper… không nhất thiết phải là Singleton.
Runtime
– Không “thân thiện” với theading.
Design
– Coupling: Vì là global state nên các thành phần bị gắn chặt với nhau.
– Khó / không thể viết test.
– Viết dễ sai sót, để lại lỗ hổng (xem bài trước Singleton có thực sự dễ?).

Nên dùng như thế nào?
Như vậy, ta thấy rằng đa phần những thứ dở của design pattern này là ở tư tưởng global state. Vậy nên những ai yêu thích functional programming thì sẽ rất anti-pattern này. Cũng đúng thôi, GoF sinh ra cho OOP, không phải FP. Và thời đại của GoF (1994) cũng không quan tâm nhiều tới concurrency – bài toán trở thành rất cơ bản trong thời đại này. Bởi vậy, việc sử dụng Singleton có chút thay đổi. Có 3 điều cần chú ý:

  1. Concurrency: Global state là điều tệ hại cho concurrency. Hãy giảm thiểu tối đa nếu có thể. Global state bẻ cong cách suy nghĩ về luồng và gây mệt mỏi cho việc debug trong concurrency. Nếu bạn muốn thiết kế hệ thống tối ưu hiệu năng và concurrency thì không sử dụng Singleton cũng là một ý hay.
  2. Memory:
    Lưu cái gì? Global state cũng là một ý hay vì khiến việc thiết kế và lập trình dễ dàng hơn, nó chỉ không hay khi bạn không cân nhắc tới nên lưu gì. Rất nhiều thứ có thể nhìn dưới góc độ một instance nếu chúng ta không có khả năng khái quát hoá. Logger là Singleton không? Hay có errorlog, accesslog? Database là single thì lưu cả database? Hay chỉ connection? Hay chỉ connectionString? Lưu ít nhất có thể.
    Lưu khi nào? Nhiều người hay gắn Singleton với life cycle của cả ứng dụng, kèm theo việc lưu trữ nhiều, hoặc giữ strong reference dẫn đến GC không thể hoạt động; sớm muộn gì cũng gây ra memory leak. Khởi tạo muộn nhất có thể, giải phóng sớm nhất có thể.
  3. Language: Cần lưu ý cách sử dụng trong từng ngôn ngữ, mỗi ngôn ngữ khác nhau sẽ có vấn đề khác nhau. Dù design pattern là mức thiết kế song đừng mang nguyên cách cài đặt từ ngôn ngữ này sang ngôn ngữ khác, hãy nhìn vào diagram và đặc trưng ngôn ngữ.