Singleton Pattern

Giới thiệu Singleton Pattern

Để bắt đầu cho loạt 26 pattern muốn giới thiệu, mình xin được chọn Singleton thay vì làm theo thứ tự ABC. Lý do là Singleton không quá khó để hiểu mà lợi ích đem lại không phải nhỏ, tuy nhiên, dường như nó đang bị xem nhẹ và có nhiều hiểu nhầm. Singleton xứng đáng nhận được nhiều sự trân trọng hơn.

Nếu bạn mới lần đầu nghe thấy hai từ “design pattern” hoặc vừa làm quen với nó thôi, chắc bạn sẽ muốn đọc bài giới thiệu sơ lược của mình tại đây. Trong bài đó, mình trình bày design pattern là gì, có những loại nào, và vài lời bình luận xàm.

SINGLETON

Bài viết này, cũng như những bài khác về design pattern, mình sẽ chia thành 5 phần:

  • Ý tưởng chính
  • Vấn đề cần giải quyết
  • Cấu trúc
  • Code ví dụ
  • Lưu ý
  • Vài lời bình luận

Ý tưởng chính

  • Đảm bảo class chỉ có một thể hiện (instance) duy nhất
  • Đảm bảo có thể truy cập mọi lúc, mọi nơi
  • Khởi tạo just-in-time hoặc khởi tạo ở lần gọi đầu tiên

Vấn đề cần giải quyết

Chương trình cần đối tượng chỉ có một thể hiện duy nhất. Cùng với đó là khả năng có thể truy cập đối tượng mọi lúc, mọi nơi và lazy-init cũng rất cần thiết.

Một ví dụ cho Singleton trong đời sống thực là Ban giám đốc của một công ty. Tại mỗi thời điểm, công ty có một và chỉ một Ban giám đốc mà cách thức hoạt động, quyền hạn,… đã được quy định trước. Bất kể giám đốc tên họ là gì, nam hay nữ, theo tôn giáo nào hay không,… ta luôn “truy cập” thông qua chức danh giám đốc trong toàn bộ công ty.

Trong lĩnh vực phần mềm, khi bạn cần đối tượng thực hiện chức năng cụ thể, như ghi log cho chương trình, nó nên được viết thành Singleton. Bất kể tại phần nào trong chương trình, chỉ có một thể hiện, chỉ có một cách truy cập đến Logger.

Cấu trúc

Để biến một class thành Singleton, cần đảm bảo rằng:

  • Định nghĩa một attribute là private static và đó là thể hiện duy nhất của class này
  • Định nghĩa public static getInstance() dùng để khởi tạo đối tượng (hàm accessor)
  • Thực hiện lazy-init trong hàm accessor (chỉ khi gọi mới khởi tạo thể hiện)
  • Constructor (hoặc các constructor) là private hay protected, vì bạn không muốn client tạo nhiều thể hiện
  • Client chỉ có thể gọi hàm accessor khi muốn có thể hiện của class

Code ví dụ

Code ví dụ này được viết bằng Java, các ngôn ngữ khác cũng sẽ tương tự.

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Lưu ý

Các pattern khác có thể dùng cùng Singleton. Chẳng hạn, Abstract Factory, Builder, Prototype (sẽ trình bày cụ thể trong từng bài riêng cho mỗi pattern). Các đối tượng Facade và State cũng thường là Singleton.

Không nên hiểu máy móc rằng Singleton nghĩa là tồn tại chính xác 1 thể hiện. Có thể có những thể hiện khác nhau cho những mục đích khác nhau. Đây cũng là ưu điểm của Singleton so với việc dùng biến toàn cục (global variable).

Singleton là toàn cục. Vì vậy, khi đơn giản là muốn truyền một đối tượng A cho đối tượng B xử lý, hãy cân nhắc xem bạn có thật sự cần một đối tượng toàn cục hay không. Giống như thời trang vậy, che bao nhiêu, khoe bao nhiêu, bạn phải tự tìm lấy một điểm cân bằng.

Thận trọng với đa luồng (multithreading). Hai luồng khác nhau có thể gọi phương thức khởi tạo ở cùng một thời điểm và sinh ra hai thể hiện. Trong khi đó, đồng bộ (synchronized) phương thức khởi tạo lại ảnh hưởng tới hiệu suất.

Singleton: pattern hay anti-pattern?

Singleton có phải anti-pattern hay không? Tức là, nó nên tránh hay không? Điều này còn phụ thuộc nhiều thứ và sẽ gây nhiều tranh cãi. Dưới đây là một ý kiến mà bạn nên cân nhắc.

Có bốn đại cao thủ, mà đồng đạo code lâm thường gọi là Tứ Nhân Bang (Gang of Four), gồm Erich Gamma, Richard Helm, Ralph Johnson, và John Vlissides. (Đừng nhầm với “bè lũ bốn tên” bên Tung Của nha). Ngày 21/10/1994, họ có tung vào trong giang hồ một cuốn bí kíp, gọi là “Design Patterns: Elements of Reusable Object-Orientated Software”. Nó cổ vũ người người, nhà nhà sử dụng design pattern theo một cách “có quy củ”.

Bên cạnh các chiêu thức khác, Singleton được yêu mến hết mực, thậm chí so bề tài sắc lại là phần hơn. Ấy cũng vì nó dễ hiểu ý tưởng, dễ đem vào sử dụng. Nhưng hỡi ôi, kiếm tốt rồi cũng đứt tay người. Singleton trở thành con dao hai lưỡi đâm vào sau lưng nhiều đại hiệp. Những người thiết kế hệ thống quá lạm dụng nó đã khiến họ phải nhận lời cay đắng từ các lập trình viên hì hụi refactor.

Và miệng đời gán cho Singleton cái danh ác quỷ.

Tại sao lại có lời cay nghiệt như vậy? Vì những người thiết kế phớt lờ bốn lưu ý ở trên. À quên, hồi ấy đã làm gì có mấy lưu ý đó. Họ đã làm thế này. Đầu tiên, họ ép những đối tượng có thể có nhiều thể hiện trở nên chỉ có một thể hiện. Và code bỗng không còn dễ thay đổi, dễ cập nhật nữa, không còn flexible. Và tiếp theo, tai hại hơn, họ khiến chương trình khó test, khó debug hơn bằng cách dùng chiêu thức singleton mà họ ưa thích. Rất khó để viết unit test cho những đoạn code dùng chiêu thức ấy.

Chính vì những tác hại khôn lường mà singleton có thể đem lại, chúng ta nên nghiêm túc nhìn nhận rằng: nó quả thực là một anti-pattern. Khi muốn áp dụng nó, hãy chắc chắn rằng bạn đã xem xét các lưu ý bên trên. Dấu hiệu để lựa chọn Singleton chính là: liệu việc tạo ra nhiều thể hiện của class có gây nguy hiểm hay không (chẳng hạn một công ty mà có nhiều giám đốc điều hành). Đồng thời, cũng cần đảm bảo việc dùng Singleton không ảnh hưởng đến việc thực thi, khả năng nâng cấp, bảo trì của chương trình.

Mong rằng bài viết này sẽ giúp ích cho việc lập trình của bạn. Và hãy giữ cho việc áp dụng design pattern là “healthy & balance”.


Tham khảo

Singleton Design Pattern, Source Making

Design Patterns – Singleton pattern, Viblo

The singleton pattern: evil or just misused?, David Litvak, Contentful

Design Patterns (Book), Wikipedia

9 bình luận trong “Giới thiệu Singleton Pattern”

Trả lời

Email của bạn sẽ không được hiển thị công khai.