Trong Java, các design pattern (mẫu thiết kế) đóng vai trò quan trọng trong việc cung cấp các giải pháp đã được kiểm chứng cho các vấn đề thiết kế phần mềm phổ biến. Java Development Kit (JDK) của Java bao gồm nhiều ví dụ về các design pattern, cho phép lập trình viên tận dụng các giải pháp này mà không cần phải triển khai lại từ đầu. Trong bài viết này, chúng ta sẽ xem xét một số design pattern phổ biến được áp dụng trực tiếp trong các thư viện JDK của Java.
Singleton Pattern
Giới thiệu Singleton Pattern
Singleton là một design pattern đảm bảo rằng chỉ có duy nhất một instance của một lớp được tạo ra và cung cấp một điểm truy cập toàn cục cho instance đó. Điều này rất hữu ích khi bạn cần kiểm soát số lượng instance của một lớp, ví dụ như trong trường hợp quản lý kết nối cơ sở dữ liệu hoặc các thành phần chia sẻ trong hệ thống.
Singleton trong JDK
Một ví dụ nổi bật về Singleton trong JDK là lớp Runtime
. Lớp này quản lý thông tin về môi trường runtime của Java và không cho phép tạo nhiều instance. Bạn có thể lấy instance của lớp này thông qua phương thức tĩnh getRuntime()
.
Runtime runtime = Runtime.getRuntime();
runtime.gc(); // Gọi garbage collector thông qua instance singleton
Phương thức getRuntime()
luôn trả về cùng một instance của Runtime
, đảm bảo rằng có duy nhất một đối tượng Runtime
tồn tại trong JVM.
Factory Method Pattern
Giới thiệu Factory Method Pattern
Factory Method là một pattern cung cấp một giao diện để tạo đối tượng nhưng cho phép các lớp con quyết định lớp nào sẽ được khởi tạo. Điều này giúp tăng tính linh hoạt và mở rộng của ứng dụng mà không cần phải thay đổi mã nguồn ở nhiều nơi.
Factory Method trong JDK
Một ví dụ điển hình của Factory Method trong JDK là các phương thức valueOf()
trong các lớp wrapper như Integer
, Boolean
, và Double
. Thay vì tạo đối tượng trực tiếp thông qua từ khóa new
, bạn có thể sử dụng các phương thức factory để tạo ra chúng.
Integer num = Integer.valueOf(5);
Boolean flag = Boolean.valueOf(true);
Các phương thức này giúp tối ưu hóa việc tạo đối tượng, đặc biệt khi các giá trị tương ứng đã tồn tại, chúng có thể trả về đối tượng đã được tạo từ trước thay vì tạo một đối tượng mới.
Builder Pattern
Giới thiệu Builder Pattern
Builder Pattern tách rời quá trình tạo một đối tượng phức tạp ra khỏi cấu trúc của nó, cho phép việc tạo đối tượng trở nên linh hoạt và dễ hiểu hơn. Pattern này thường được sử dụng khi đối tượng có nhiều tham số hoặc yêu cầu cấu hình phức tạp trước khi khởi tạo.
Builder Pattern trong JDK
Lớp StringBuilder
và StringBuffer
trong JDK là những ví dụ điển hình của Builder Pattern. Cả hai lớp này đều cung cấp khả năng tạo chuỗi ký tự một cách hiệu quả, tránh việc tạo quá nhiều đối tượng String
không cần thiết.
StringBuilder builder = new StringBuilder();
builder.append("Hello, ");
builder.append("world!");
String result = builder.toString();
Với Builder Pattern, bạn có thể tạo và thay đổi chuỗi nhiều lần mà không phải tạo ra nhiều đối tượng trung gian. Điều này giúp cải thiện hiệu suất, đặc biệt khi làm việc với các chuỗi lớn.
Observer Pattern
Giới thiệu Observer Pattern
Observer Pattern định nghĩa mối quan hệ phụ thuộc một-nhiều giữa các đối tượng, trong đó khi một đối tượng thay đổi trạng thái, tất cả các đối tượng phụ thuộc của nó sẽ được thông báo và tự động cập nhật. Đây là một cách tuyệt vời để thiết kế các hệ thống mà các thành phần cần liên lạc với nhau nhưng không cần biết chi tiết về nhau.
Observer Pattern trong JDK
Lớp Observable
và giao diện Observer
trong JDK là ví dụ rõ ràng của Observer Pattern. Bạn có thể sử dụng các lớp này để tạo ra các đối tượng có thể thông báo thay đổi trạng thái của mình cho các observer đã đăng ký.
import java.util.Observable;
import java.util.Observer;
class MyObservable extends Observable {
void changeState() {
setChanged(); // Đánh dấu rằng đối tượng đã thay đổi
notifyObservers(); // Thông báo cho các observer
}
}
class MyObserver implements Observer {
public void update(Observable o, Object arg) {
System.out.println("State changed!");
}
}
MyObservable observable = new MyObservable();
MyObserver observer = new MyObserver();
observable.addObserver(observer);
observable.changeState(); // Observer sẽ nhận thông báo
Mặc dù các lớp Observable
và Observer
đã được đánh dấu là deprecated từ Java 9 trở đi, chúng vẫn là ví dụ điển hình về việc triển khai Observer Pattern trong JDK.
Strategy Pattern
Giới thiệu Strategy Pattern
Strategy Pattern cho phép thay đổi thuật toán thực thi của một đối tượng một cách linh hoạt mà không cần thay đổi lớp đó. Điều này rất hữu ích khi bạn cần thay đổi cách thức hoạt động của một đối tượng trong lúc runtime mà không cần sửa đổi code gốc.
Strategy Pattern trong JDK
Một ví dụ điển hình về Strategy Pattern trong JDK là các lớp Comparator
và Collections.sort()
. Phương thức sort()
cho phép bạn cung cấp một đối tượng Comparator
để thay đổi cách so sánh các phần tử trong danh sách.
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2); // So sánh theo thứ tự từ điển
}
});
Với Strategy Pattern, bạn có thể dễ dàng thay đổi thuật toán so sánh mà không cần thay đổi lớp Collections
hay các lớp liên quan khác.
Adapter Pattern
Giới thiệu Adapter Pattern
Adapter Pattern cho phép các lớp với giao diện không tương thích làm việc cùng nhau bằng cách tạo ra một lớp adapter để đóng vai trò làm cầu nối giữa các lớp. Điều này rất hữu ích khi bạn cần tích hợp các lớp cũ hoặc bên thứ ba vào hệ thống hiện tại.
Adapter Pattern trong JDK
Lớp InputStreamReader
trong JDK là một ví dụ điển hình của Adapter Pattern. Lớp này chuyển đổi một InputStream
(luồng byte) thành Reader
(luồng ký tự), giúp các thành phần không tương thích làm việc cùng nhau.
InputStream inputStream = new FileInputStream("example.txt");
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
Lớp InputStreamReader
đóng vai trò là adapter giữa InputStream
và Reader
, cho phép bạn xử lý dữ liệu theo định dạng ký tự thay vì byte.
Như vậy, các design pattern như Singleton, Factory, Builder, Observer, Strategy, và Adapter đều được áp dụng rất hiệu quả trong các thư viện của JDK. Chúng giúp tăng tính linh hoạt, khả năng mở rộng và tái sử dụng của mã nguồn, đồng thời cung cấp các giải pháp đã được kiểm chứng cho các vấn đề thiết kế phần mềm phức tạp.