Chuyển đến nội dung chính

Design Pattern – Singleton

Mẫu thiết kế Singleton đảm bảo rằng một lớp chỉ có một thể hiện (instance) duy nhất. Do thể hiện này có tiềm năng sử dụng trong suốt chương trình, nên mẫu thiết kế Singleton cũng cung cấp một điểm truy cập toàn cục đến nó.
Hộp thoại Find là một ví dụ điển hình của mẫu thiết kế Singleton. Dù bạn chọn menu hoặc nhấn phím tắt (Ctrl +F) nhiều lần thì cũng chỉ có duy nhất một hộp thoại xuất hiện.

1. Cài đặt

singleton
Mẫu thiết kế Singleton đơn giản và dễ áp dụng, chỉ cần bổ sung vài dòng lệnh trong lớp muốn chuyển thành Singleton.
– Dữ liệu thành viên instance (private và static) là đối tượng duy nhất của lớp Singleton.
– Constructor của lớp Singleton được định nghĩa thành protected hoặc private để người dùng không thể tạo thực thể trực tiếp từ bên ngoài lớp.
– Phương thức getInstance() dùng để khởi tạo đối tượng duy nhất, định nghĩa thành public và static. Client chỉ dùng getInstance() để tạo đối tượng cho lớp Singleton.
– Thực hiện khởi tạo chậm (lazy initialization) trong getInstance(): chỉ khi gọi phương thức getInstance() mới khởi tạo đối tượng. Phương thức này trả về một thể hiện mới hay null tùy thuộc vào một tham số kiểu boolean dùng như cờ hiệu bào xem lớp Singleton đã tạo thể hiện hay chưa.
Trong chế độ multithreading, mẫu thiết kế Singleton có thể làm việc không tốt: do getInstance() không an toàn thread, hai thread có thể gọi phương thức sinh đối tượng cùng một thời điểm và hai thể hiện sẽ được tạo ra. Nếu đồng bộ (synchronized) phương thức getInstance() để an toàn thread sẽ dẫn đến giảm hiệu suất chương trình.
Có nhiều giải pháp cho vấn đề này: double-checked locking, enum, class loader (static block initialization).
– double-checked locking: kiểm tra sự tồn tại thể hiện của lớp, với sự hổ trợ của đồng bộ hóa, hai lần trước khi khởi tạo. Phải khai báo volatile cho instance để tránh lớp làm việc không chính xác do quá trình tối ưu hóa của trình biên dịch.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package demosingleton;
 
class Singleton {
    private static volatile Singleton instance;
    private Singleton(){}
     
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
 
public class DoubleCheckLockingSingleton {
 
    public static void main(String[] args) {
        System.out.println("--- Singleton Pattern ---");
        Singleton single1 = Singleton.getInstance();
        Singleton single2 = Singleton.getInstance();
        if (single1.equals(single2)) {
            System.out.println("Unique Instance");
        }
    }
}
-class loader (Java):
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package classloadersingleton;
 
class Singleton {
 
    private static class Instance {
 
        static final Singleton instance = new Singleton();
    }
 
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return Instance.instance;
    }
}
 
public class ClassLoaderSingleton {
 
    public static void main(String[] args) {
        System.out.println("--- Singleton Pattern ---");
        Singleton single1 = Singleton.getInstance();
        Singleton single2 = Singleton.getInstance();
        if (single1.equals(single2)) {
            System.out.println("Unique Instance");
        }
    }
}

2. Liên quan:

– Abstract Factory: thường là Singleton để trả về các đối tượng factory duy nhất
– Builder: dùng để xây dựng một đối tượng phức tạp, trong đó Singleton được dùng để tạo một đối tượng truy cập tổng quát (Director).
– Prototype: dùng để sao chép một đối tượng, hoặc tạo ra một đối tượng khác từ Prototype của nó, trong đó Singleton được dùng để chắc chắn chỉ có một Prototype.

3. Java API

Lớp java.lang.Runtime là lớp Singleton, để lấy được đối tượng duy nhất của nó, ta gọi phương thức getRuntime(). Tương tự, lớp java.awt.Desktop cũng là lớp Singleton, tạo đối tượng duy nhất bằng phương thức getDesktop(). Singleton không phổ biến như ta nghỉ, nó chỉ áp dụng với lớp cần bảo đảm chỉ có một thể hiện duy nhất.

4. Sử dụng

Ta muốn:
  • Đảm bảo rằng chỉ có một thể hiện của lớp.
  • Quản lý việc truy cập tốt hơn vì chỉ có một thể hiện duy nhất.
  • Quản lý số lượng thể hiện của một lớp. Trường hợp này không nhất thiết chỉ có một thể hiện, bạn cần kiểm soát số lượng thể hiện trong giớn hạn chỉ định

Vài phút hiểu về Java Singleton Pattern

Java Singleton Pattern là gì thế? Liệu rằng vài phút có hiểu hết được Java Singleton Pattern? Nó có nhiều lắm không? Những câu hỏi này mình sẽ giải đáp cho các bạn khi đọc hết những nội dung bên dưới nhé :)))
Vài phút hiểu về Java Singleton Pattern

Trả lời cho câu hỏi Java Singleton Pattern là gì?

Singleton Pattern là một mẫu thiết kế (design pattern) có tên là singleton, thuộc nhóm Creational Design Pattern(nhóm này có 5 design pattern, singleton là 1 trong số 5 pattern đó).

Ngoài lề một tý trước khi hiểu tiếp về Singleton Pattern

Khi bạn định nghĩa một class (chẳng hạn như SinhVien), tại nhiều class khác (có thể là class SinhVienDemo1, SinhVienDemo2, QuanLySinhVien) bạn gọi đối tượng từ class SinhVien đó để sử dụng.
sv1sv2sv3 là 3 đối tượng thuộc class SinhVien, 3 đối tượng này là 3 thể hiện (instance) của class đó (đối tượng là thể hiện của class) và được gọi từ các class khác nhau. Các thể hiện này sẽ được trỏ đến các vùng nhớ khác nhau trong java virtual machine, đồng nghĩa với việc dữ liệu của 3 đối tượng sẽ khác nhau, không liên quan với nhau.
Nhưng đời đâu như là mơ, sếp mình lại bảo muốn có 3 đối tượng (3 tên đối tượng sv1, sv2, sv3), nhưng lại cùng trỏ đến một vùng nhớ trong java virtual machine thôi. Như vậy, chẳng khác gì cho 3 chàng trai cùng làm chồng của 1 cô gái (em ấy sướng quá rồi 😀 ).
Sao sếp lại làm chuyện ngược đời thế? Phải chăng chương trình chỉ quản lý 1 sinh viên duy nhất (lớp này ế nhỉ)
Nghe lời sếp, thế thì có giải pháp thế này:
Ở class SinhVienDemo1 bạn tạo ra đối tượng static sv1 với phạm vi truy cập public
Ở class SinhVienDemo2QuanLySinhVien bạn chỉ cần tạo sv2, sv3 rồi nhận giá trị thôi
Hơi xàm rồi đấy, có nhiều cách để bạn thực hiện việc này, ở trên là 1 ví dụ. Tuy nhiên, với những cách thế này thì nhiều lúc sv2, sv3 bị dính NULL đó nhé, rồi tự nhiên muốn sử dụng SinhVien lại phải dùng SinhVienDemo1 làm class trung gian. Ôi! Cuộc đời sao bế tắc thế, ở nhà làm nông, chăn trâu chăn gà còn sướng hơn. 🙁

Được anh sếp giới thiệu giải pháp Singleton Pattern

Chưa cần biết cách triển khai Singleton nó thế nào, cứ xem ví dụ đã nhé.
Ở class SinhVien bạn tạo một phương thức static svduynhat() trả về đối tượng của chính class SinhVien (phương thức này có phạm vi truy cập là public)
Rồi thì ở các class SinhVienDemo1SinhVienDemo2QuanLySinhVien chỉ cần tạo các đối tượng sv1, sv2, sv3 thế này
Như vậy, khi dùng Singleton Pattern mà sếp hướng dẫn, bạn đã thấy sung sướng hơn chưa, từ bỏ trâu gà quay lại nghiệp Code nhé 😀
[notification type=”alert-info” close=”false” ]
Lưu ý
Ví dụ trên chỉ code đơn giản theo hướng giải quyết của singleton pattern chứ chưa thật sự đầy đủ, bạn theo dõi những gì bên dưới rồi quay lại hoàn thành ví dụ nhé 😀
Vả lại, Mình chưa thấy một ứng dụng thực tế nào như ví dụ trên cả, tất cả chỉ là ví dụ để bạn hiểu singleton nó được áp dụng thế nào thôi 😀
[/notification]

Tiếp tục bàn về khái niệm Singleton Pattern

Từ ví dụ đơn giản trên, bạn có thể thấy Singleton Pattern giải quyết ổn thoả việc tạo ra một đối tượng (thể hiện) duy nhất từ một lớp để sử dụng trong suốt quá trình chương trình chạy.

Singleton Pattern nhiều lắm không?

Không rõ ở đây nhiều lắm là nhiều về cái gì? Tuy nhiên mình vẫn trả lời thế này.
Về mặt chức năng: Singleton đảm bảo rằng class chỉ có duy nhất một thể hiện (hay đối tượng) được tạo ra và nó sẽ cung cấp một phương thức để bạn truy cập đến thể hiện đó.
Về mặt nguyên tắc: Dù bạn thực hiện bằng cách nào đi chăng nữa thì cũng phải dựa vào các nguyên tắc sau:
  1. Constructor của class dùng Singleton Pattern phải có phạm vi truy cập là private để không thể tạo thể hiện của class từ bên ngoài.
  2. Khai báo private static cho tên biến của thể hiện đã tạo để đảm bảo thể hiện chỉ được tạo ra trong class đó thôi.
  3. Có một phương thức public để trả về thể hiện đã được tạo.
Như vậy chỉ cần nắm rõ chức năng và 3 nguyên tắc của singleton pattern là bạn có thể áp dụng vào project của mình, cách đặt tên thể hiện hay phương thức thì tuỳ bạn thôi. Vậy bạn thấy có nhiều không?
Đến đây thì thật sự không nhiều kiến thức về singleton pattern để bạn nhớ đâu, chỉ cần nhớ những điều trên là được. Tuy nhiên, bạn có đảm bảo rằng cách code của bạn theo nguyên lý singleton pattern thật sự hiệu quả và thực thi nhanh chóng. Không thể dám chắc đúng không? Vì bạn có thể không phải là một chuyên gia. 😀

Cách chuyên gia code theo nguyên lý singleton pattern

Đã gọi là chuyên gia thì ắt hẳn phải là một bậc thầy phải không các bạn. Vậy những cách áp dụng singleton của các bậc thầy như thế nào thì cùng mình điểm qua vài phương pháp chính sau nhé.

1. Eager initialization

Thể hiện của lớp sẽ được tạo ngay khi được gọi đến. Đây là cách dễ nhất nhưng nó có một nhược điểm là mặc dù thể hiện (instance) đã được khởi tạo nhưng có thể sẽ không dùng tới.

2. Static block initialization

Cũng tương tự Eager initialization nhưng có thêm phần static block cung cấp thêm tuỳ chọn để xử lý việc khác (chẳng hạn như lỗi).

3. Lazy Initialization

Là một cách làm mang tính mở rộng hơn so với 2 cách làm trên, hoạt động tốt trong từng thread đơn lẻ. Và tất nhiên vấn đề xấu sẽ xảy ra nếu chúng ta đang dùng nó với multi thread. (Để khắc phục nhược điểm này, làm theo cách thứ 4).

4. Thread Safe Singleton

Sử dụng thêm từ khóa synchronized trong phương thức trả về thể hiện của lớp. (Dùng trong các chương trình multi thread)

5. Bill Pugh Singleton Implementation

Với cách làm này sẽ tạo ra static nested class để tạo thể hiện (Cách mình hay dùng)

6. Using Reflection to destroy Singleton Pattern

Reflection được dùng để destroy tất cả các singleton mà chúng ta đã tạo ra.

7. Enum Singleton

Nếu như bạn đã đọc qua cuốn Effective Java sẽ thấy Joshua Bloch đã khẳng định rằng “sử dụng enum type là cách tốt nhất để triển khai Singleton” cho bất kì ngôn ngữ nào mà hỗ trợ enums. (Tuy nhiên mình không tin cho lắm 😀 )

8. Serialization and Singleton

Serialization là kỹ thuật sắp xếp đối tượng cần lưu trữ một cách tuần tự. Ví dụ bên dưới là quá trình đọc ghi dữ liệu tích hợp Singleton.
Còn đây là bảng đo hiệu suất khi sử dụng các phương pháp này
Vài phút hiểu về Java Singleton Pattern
Có thể thấy cách làm khi sử dụng inner-class static (hay Bill Pugh Singleton Implementation) đạt hiệu suất cao nhất.
Như vậy, có nhiều chuyên gia, có nhiều cách thực hiện. Có cách tốt, cách tệ, cách có hiệu suất cao, cách có hiệu suất thấp, cách dùng tốt với single thread, cách dùng tốt với multi thread,…nên bạn phải nhớ rằng bản chất mới là quan trọng, cách thức thực hiện chẳng là gì cả. Ở một số dự án bạn thực hiện theo phương pháp này, nhưng một số dự án bạn bắt buộc phải thực hiện theo một phương pháp khác. Các phương pháp trên của các chuyên gia đã được kiểm chứng và sử dụng trong nhiều dự án, bạn có thể sử dụng một trong những phương pháp đó nhé. (Có thể chọn cách Bill Pugh Singleton Implementation vì bạn cũng thấy hiệu suất của nó rồi đó 😀 )

Ứng dụng của Singleton Pattern

Bạn thử hình dung trong đầu mình rằng đã hiểu về Singleton Pattern như thế nào rồi nhé. Cứ việc tập trung vào chức năng của nó: “đảm bảo rằng class chỉ có duy nhất một thể hiện (hay đối tượng) được tạo ra và nó sẽ cung cấp một phương thức để bạn truy cập đến thể hiện đó” để có thể ứng dụng vào trong các dự án mà bạn cần đến.
Dưới đây là một số ứng dụng của singleton pattern để bạn tham khảo thêm:
  • Vì class dùng Singleton chỉ tồn tại 1 Instance (thể hiện) nên nó thường được dùng cho các trường hợp giải quyết các bài toán cần truy cập vào các shared resource hoặc implement cho các Logger class (logging), Configuration class, DAO, drivers objects, caching hoặc thread pool.
  • Một số design pattern khác cũng sử dụng Singleton để triển khai: Abstract Factory, Builder, Prototype, Facade,…
  • Singleton Pattern cũng được sử dụng trong một số class của core java, ví dụ như: java.lang.Runtime, java.awt.Desktop.

Sếp nói thêm

À thật ra thì không phải sếp nói là mình tóm lại thôi nhé. 😀
Đó là tất cả những gì mình hiểu, tìm hiểu và vận dụng Singleton Pattern. Mình khuyên các bạn chỉ cần hiểu đơn giản thôi, đừng quan trọng hoá vấn đề.
Phương pháp Singleton mình hay sử dụng là Bill Pugh Singleton Implementation (vì nó có hiệu suất cao), Lazy Initialization (vì tính đơn giản của nó nếu không dùng đến multi thread) và Thread Safe Singleton (tuy hiệu suất thấp nhưng là cách khá an toàn khi có dùng multi thread). Code ví dụ thì đã có ở trên, các bạn xem lại nhé.
Cuối cùng, hẹn gặp lại các bạn ở những bài viết phân tích sâu hơn về từng phương pháp của các chuyên gia để thấy ưu nhược điểm của từng cái và các ví dụ mẫu cho từng phương pháp đó.
Nếu có vấn đề gì cần thảo luận thì để lại bình luận bên dưới bạn nhé <3

Nhận xét

Bài đăng phổ biến từ blog này

Entry Test của FPT

IQ - Kiểm tra tư duy logic (8/20) - GMAT- Kiểm tra khả năng tính toán trong thời gian ngắn (8/20) - Tiếng Anh (18-> 25/50) - Các bài thi chuyên môn - FE (8/20) IQ: lên mạng tìm "IQ test" là ra đầy. + GMAT: Những câu trắc nghiệm tính toán đơn giản kiểu như sau:  1 . Một shop thời trang sale off quần jeans 15 %, quần jeans giá 450 $, người mua đưa 500 $, hỏi cashier trả lại bao nhiêu $ tiền thừa.? 2 . 100 % là 180 , vậy 150 là bao nhiêu %? Tiếng anh: Cỡ như thi TOEIC thôi. Chuyên môn: Mobile thì trắc nghiệm Java. Qúa trình tuyển như sau :v Lần 1: Test IQ, Tiếng Anh( mình làm í ẹ khoảng 50% mà vẫn được) , Java Lân 2: được gọi điện lên :)) + Gioi thiệu bản thân + Họ chỉ hỏi các câu căn bản như: -. OOP: là gì, 4 tính chất, ví dụ, khác nhau giữa interface và abstract - CODE: hầu toàn các bài toán vòng for :)) , cẩn thận mấy câu kế thừa. SQL (distinct, view, function, cursor, store procedure, ...v.v.), nhớ có câu cộng 2 số int không dùng biến đệm hơi khoai haha + Nói ch...

Java: Java Package-Thư viện trong Java

Giới thiệu về Package Các bạn mới học lập trình Java thường không dể ý tới package vì các bạn toàn tạo file .java vào cùng 1 chỗ, không cần sắp xếp, không cần quản lý truy nhập. Nhưng để tăng kỹ năng lập trình với Java, các bạn cần phải tìm hiểu về package trong Java. Các bạn có thể tham khảo định nghĩa sau: Package được dùng để đóng gói các lớp trong chương trình lại với nhau thành một khối. Đây là cách tốt nhất để lưu trữ các lớp gần giống nhau hoặc có cùng một module thành một khối thống nhất – để đáp ứng 1 khối chức năng. Từ đây mình sẽ giới thiệu thêm với các bạn các câu lệnh nhâp khẩu,nó có định dạng như sau : Định dạng :  import javaPackageNameImport;    Nó giống như khai báo thư viện ở các ngôn ngữ lập trình khác.Như vậy,chỉ khi các bạn nhập khẩu chúng,các bạn mới có thể sử dụng thư viện mà chúng cung cấp cho ta. VD :    import java.util.Date;   import java.text.SimpleDateFormat; Lưu ý : -Các câu lệnh nhập khẩu rất nhiều và...

Khác nhau giữa Array và ArrayList và HashMap

Collection bản chất là tập các lớp dùng để lưu trữ danh sách và có khả năng tự co giãn khi danh sách thay đổi : Thêm , sửa , xóa , chèn … Hai lớp Collection thường được sử dụng nhiều nhất là ArrayList và Hashmap Giới thiệu về ArrayList ArrayList sử dụng cấu trúc mảng để lưu trữ phần tử , tuy nhiên có hai đặc điểm khác mảng : Không cần khai báo trước kiểu phần tử . Không cần xác định trước số lượng phần tử ( kích thước mảng ). N ó có kh ả năng truy c ậ p ph ầ n t ử ng ẫ u nhiên (Do th ừ a k ế t ừ interface RandomAccess ). P hương thức khởi tạo ● ArrayList () ● ArrayList (Collection c) ● ArrayList ( int initialCapactity ) Các phương thức chính ● add(Object o) ● remove(Object o) ● get( int index) ● size() ● isEmpty () ● contains(Object o) ● clear() Giới thiệu về HashMap ● Là ki ể u t ậ p h ợ p t ừ đ i ể n, HashMap cho phép truy xu ấ t tr ự c ti ế p t ớ i m ộ ...