Singleton pattern – mẫu thiết kế tạo object duy nhất

    0

    Singleton pattern là mẫu thiết kế thuộc nhóm creational. Singleton đảm bảo rằng sẽ chỉ có một object duy nhất của class được khởi tạo. Một yếu tố quan trọng khác của Singleton là object được xem như một biến “toàn cục”, nghĩa là object đó có thể được truy xuất từ khắp mọi vị trí trong chương trình.

    Khi một class được xây dựng theo mẫu Singleton, nó chỉ có thể được khởi tạo một lần duy nhất. Mọi yêu cầu khởi tạo khác sẽ đều được điều hướng tới object duy nhất được khởi tạo. Thêm vào đó, object của một Singleton class sẽ không được khởi tạo cho đến khi nó thực sự được sử dụng.

    Trong bài học này chúng ta sẽ xem xét chi tiết ý tưởng, thiết kế, các thực thi và vận dụng của mẫu thiết kế này.

    Singleton pattern

    Nếu bạn đang sử dụng windows, có thể bạn đã từng sử dụng Control Panel. Bạn có để ý rằng, nếu bạn đang mở cửa sổ Control Panel, sau đó lại mở nó từ một nơi khác (ví dụ từ Start menu), bạn vẫn chỉ thấy một cửa sổ Control Panel.

    Tuy nhiên, nếu bạn mở Chrome, rồi liên tục click shortcut của Chrome từ Start menu hoặc desktop, bạn sẽ thu được rất nhiều cửa sổ Chrome khác nhau.

    Cách thức chạy chương trình của Control Panel có thể xem như tương đồng với ý tưởng của mẫu thiết kế Singleton: chỉ có một instance (object) duy nhất được tạo ra khi lần đầu cần dùng. Tất cả những truy xuất sau đó đều điều hướng tới object được tạo.

    Ở khía cạnh khác, cách mở nhiều cửa sổ Chrome cùng lúc tương tự như cách thức bạn tạo object bình thường: từ một class bạn tạo ra nhiều object để sử dụng.

    Bạn có thể đặt câu hỏi, tại sao lại giới hạn tạo một object duy nhất?

    Trong lập trình, đôi khi việc có một object duy nhất truy xuất toàn cục giúp ích rất lớn. Ví dụ, bạn cần xử lý file cấu hình của ứng dụng, bao gồm đọc file, truy xuất các thông tin, lưu lại cấu hình. Do file cấu hình thường là duy nhất, và cần truy xuất từ rất nhiều vị trí khác nhau trong chương trình, việc có một object “toàn cục” và duy nhất làm việc truy xuất cấu hình đơn giản hơn rất nhiều.

    Thiết kế UML cho Singleton pattern

    So với các mẫu thiết kế khác, Single đơn giản hơn rất nhiều, chỉ bao gồm một class duy nhất.

    Mẫu Singleton áp dụng cho một class sẵn có bằng cách thực hiện một số điều chỉnh:

    Sơ đồ UML của Singleton pattern
    • Đặt hàm tạo (constructor) là private để client code không thể khởi tạo object của class;
    • Bổ sung thành viên public static trả về object của class: Thành viên này sẽ được truy xuất từ client code để lấy object của class;
    • Bổ sung private static constructor: constructor này có nhiệm vụ khởi tạo thành viên static của class.

    Ví dụ thực thi Singleton pattern

    Hãy cùng thực hiện mẫu thiết kế này bằng C#:

    using System;
    using static System.Console;
    
    namespace P01_Concept
    {
        public sealed class Singleton
        {
            private Singleton() { }
    
            static readonly Singleton _instance = new Singleton();
    
            public static Singleton Instance => _instance;
    
            private int _count = 0;
    
            public void Increase() => _count++;
    
            public int Count => _count;
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Title = "SINGLETON DESIGN PATTERN";
    
                WriteLine($"Start: {Singleton.Instance.Count}");
    
                Singleton.Instance.Increase();
                WriteLine($"First increment: {Singleton.Instance.Count}");
    
                Singleton.Instance.Increase();
                WriteLine($"Second increment: {Singleton.Instance.Count}");
    
                Singleton.Instance.Increase();
                WriteLine($"Third increment : {Singleton.Instance.Count}");
    
                ReadKey();
            }
        }
    }

    Trong code chương trình chúng ta xây dựng class Singleton theo đúng thiết kế UML:

    • hàm tạo Singleton đặt làm private;
    • biến private static _instance chứa object của class;
    • static property Instance cho phép truy xuất đến biến _instance.

    Ngoài ra, biến _count, phương thức Increase và property Count giống như ở các class thông thường.

    Khi chạy chương trình bạn thu được kết quả như sau:

    kết quả chạy chương trình thử nghiệm singleton pattern

    Bạn có để ý, biến _count (và property Count) là những biến thành viên bình thường, nghĩa là nó chỉ tồn tại khi khởi tạo object. Mỗi lần khởi tạo object, _count sẽ lại nhận giá trị 0. Phương thức Increase là phương thức thành viên, do đó nó cũng chỉ có tác dụng trong object.

    Tuy nhiên, khi chương trình trên chạy, giá trị của biến _count tăng đúng kiểu thực hiện trên một object chứ không phải trên nhiều object khác nhau. Nghĩa là thực tế bạn chỉ có một object của Singleton được truy xuất qua property Instance.

    Với bản thực thi trên bạn có thể sẽ phân vân, liệu hai dòng lệnh

    static readonly Singleton _instance = new Singleton();
    public static Singleton Instance => _instance;

    có thực sự chỉ tạo ra một object duy nhất khi truy xuất tới property Instance lần đầu tiên?

    Bạn yên tâm. Biến _instance thực chất là một hằng số, thuộc loại runtime constant, mặc dù nó không được khai báo với từ khóa const. Runtime constant có đặc điểm là sẽ được khởi tạo duy nhất một lần, hoặc trong constructor, hoặc ngay tại nơi khai báo, và sau đó không bao giờ thay đổi giá trị nữa. Nghĩa là object nó chứa sẽ không thay đổi trong suốt vòng đời của chương trình.

    Thứ hai, vì là một “biến” static, nó được tự động khởi tạo trong lần đầu tiên client code truy xuất.

    Do Singleton không thể làm cha, nó cũng thường được đánh dấu sealed.

    Một số cách thực thi khác của Singleton

    Đôi khi bạn cũng gặp phiên bản Singleton như sau:

    public sealed class Singleton
    {
        private Singleton() { }
        static Singleton _instance;
    
        public static Singleton Instance => _instance ?? (_instance = new Singleton());        
    }

    Cách thực thi này không dựa vào đặc điểm khởi tạo của thành viên static và hằng readonly như trên. Thay vào đó, khi truy xuất tới Instance, property này mới kiểm tra xem backing field của nó, _instance, đã được khởi tạo hay chưa. Nếu _instance đã được khởi tạo, property Instance sẽ trả luôn giá trị của _instance. Nếu không nó mới khởi tạo giá trị cho _instance. Do vậy, object trả về cũng là duy nhất.

    Tuy nhiên, lưu ý, cách thức thực thi này không phù hợp với các chương trình đa luồng (không thread-safe).

    Bạn cũng có thể gặp phiên bản đặc biệt hơn của Singleton như sau:

    public class Singleton
    {
        // Private constructor
        Singleton() { }
    
        // Nested class for lazy instantiation
        class SingletonCreator
        {
            static SingletonCreator() { }
            // Private object instantiated with private constructor
            internal static readonly
            Singleton uniqueInstance = new Singleton();
        }
    
        // Public static property to get the object
        public static Singleton UniqueInstance
        {
            get { return SingletonCreator.uniqueInstance; }
        }
    }

    Phiên bản được gọi là lazy instantiation.

    Kết luận

    Trong bài học này bạn đã xem xét chi tiết ý nghĩa và cách thực thi mẫu thiết kế Singleton. Đây là một mẫu thiết kế khá đơn giản nhưng được sử dụng rất phổ biến. Singleton không chỉ được sử dụng độc lập mà thường được kết hợp trong một số mẫu thiết kế khác (như Builder, Prototype, Abstract Factory).

    Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần Bình luận ở cuối trang. Nếu cần trao đổi riêng, hãy gửi email hoặc nhắn tin qua form liên hệ. Nếu bài viết hữu ích với bạn, hãy giúp chúng tôi chia sẻ tới mọi người. Cảm ơn bạn!

    Bình luận

    avatar
      Đăng ký theo dõi  
    Thông báo về