Vấn đề quản lý bộ nhớ với đối tượng trùng lặp
Khi phát triển phần mềm, việc lưu trữ hàng loạt đối tượng giống hệt nhau thường gây lãng phí tài nguyên hệ thống. Ví dụ điển hình là trong ứng dụng bàn cờ vây, mỗi quân cờ đen/trắng có hình dáng giống nhau nhưng chỉ khác vị trí trên bàn cờ. Nếu tạo riêng từng đối tượng cho mỗi quân cờ, hệ thống sẽ tiêu tốn bộ nhớ không cần thiết. Mẫu thiết kế Flyweight giải quyết vấn đề này thông qua cơ chế chia sẻ trạng thái nội tại.
Nguyên lý hoạt động của Flyweight
Mẫu Flyweight phân tách trạng thái thành hai loại:
- Trạng thái nội tại (Intrinsic State): Dữ liệu không thay đổi theo ngữ cảnh, có thể chia sẻ giữa các đối tượng. Ví dụ: màu sắc quân cờ (đen/trắng)
- Trạng thái bên ngoài (Extrinsic State): Thông tin thay đổi theo môi trường, được truyền vào khi sử dụng. Ví dụ: tọa độ trên bàn cờ
Cơ chế hoạt động: Các đối tượng có cùng trạng thái nội tại sẽ tham chiếu đến một thể hiện duy nhất trong bộ nhớ. Khi cần sử dụng, hệ thống truyền trạng thái bên ngoài vào đối tượng chia sẻ thay vì tạo mới.
Cấu trúc mẫu thiết kế
Cấu trúc chính bao gồm:
- SharedObject: Giao diện định nghĩa phương thức chung
- ConcreteObject: Lớp cài đặt cụ thể, lưu trữ trạng thái nội tại
- ObjectPool: Quản lý nhóm đối tượng chia sẻ (享元池)
Cài đặt thực tế
Dưới đây là triển khai tối ưu cho ứng dụng cờ vây:
import java.util.HashMap;
import java.util.Map;
// Giao diện quân cờ chia sẻ
interface GoPiece {
String getType();
void render(Position pos);
}
// Lớp quân cờ tối ưu
class Stone implements GoPiece {
private final String stoneType; // Trạng thái nội tại
public Stone(String type) {
this.stoneType = type;
}
@Override
public String getType() {
return stoneType;
}
@Override
public void render(Position pos) {
System.out.printf("Hiển thị %s tại (%d, %d)%n",
stoneType, pos.x(), pos.y());
}
}
// Quản lý bộ đệm quân cờ
class StonePool {
private final Map<String, GoPiece> cache = new HashMap<>();
public GoPiece get(String type) {
return cache.computeIfAbsent(type,
t -> new Stone(t.equals("D") ? "Đen" : "Trắng"));
}
}
// Lớp tọa độ (trạng thái bên ngoài)
record Position(int x, int y) {}
Ví dụ sử dụng
Triển khai trong ứng dụng thực tế:
public class GameEngine {
public static void main(String[] args) {
StonePool pool = new StonePool();
// Lấy 3 quân đen từ bộ đệm
GoPiece dark1 = pool.get("D");
GoPiece dark2 = pool.get("D");
GoPiece dark3 = pool.get("D");
// Lấy 2 quân trắng
GoPiece light1 = pool.get("W");
GoPiece light2 = pool.get("W");
// Kiểm tra chia sẻ bộ nhớ
System.out.println("Quân đen giống nhau: " + (dark1 == dark2));
// Render với tọa độ khác nhau
dark1.render(new Position(2, 3));
dark2.render(new Position(5, 7));
light1.render(new Position(1, 4));
}
}
Kết quả thực thi:
Quân đen giống nhau: true Hiển thị Đen tại (2, 3) Hiển thị Đen tại (5, 7) Hiển thị Trắng tại (1, 4)
Hệ thống chỉ tạo duy nhất 2 đối tượng (đen và trắng) dù được gọi nhiều lần. Mỗi lần render, tọa độ mới được truyền vào như tham số bên ngoài, đảm bảo hiệu suất tối ưu.