Mẫu Chiến lược: Thiết Kế Linh Hoạt Cho Việc Chuyển Đổi Thuật Toán
I. Cốt lõi của Mẫu: Định nghĩa một chuỗi thuật toán có thể thay thế lẫn nhau
Trong phát triển phần mềm, chúng ta thường gặp các tình huống cần chọn thuật toán khác nhau dựa trên các điều kiện khác nhau. Ví dụ, trong hệ thống thương mại điện tử, tính giá cuối cùng của sản phẩm dựa trên các chương trình khuyến mãi khác nhau như giảm giá khi mua đủ số tiền, giảm giá trực tiếp, hay tặng quà.
Mẫu Chiến lược (Strategy Pattern) định nghĩa một chuỗi các thuật toán, đóng gói từng thuật toán lại và cho phép chúng có thể thay thế cho nhau. Mẫu Chiến lược giúp sự thay đổi của thuật toán độc lập với client sử dụng thuật toán, giải quyết các vấn đề cốt lõi:
- Chuyển đổi thuật toán: Chọn thuật toán phù hợp một cách linh hoạt tại thời điểm chạy dựa trên các điều kiện khác nhau.
- Tái sử dụng mã: Đóng gói các thuật toán khác nhau thành các lớp chiến lược độc lập, tăng khả năng tái sử dụng mã.
- Khả năng bảo trì: Sửa đổi và mở rộng thuật toán không ảnh hưởng đến client sử dụng thuật toán.
Ý tưởng chính và sơ đồ UML
Mẫu Chiến lược bao gồm giao diện chiến lược (Strategy), lớp chiến lược cụ thể (Concrete Strategy) và lớp ngữ cảnh (Context). Lớp ngữ cảnh giữ một tham chiếu đến giao diện chiến lược, client có thể thiết lập động tại thời điểm chạy lớp chiến lược cụ thể mà lớp ngữ cảnh sử dụng.
II. Triển khai cốt lõi: Chiến lược khuyến mãi thương mại điện tử
1. Định nghĩa giao diện chiến lược (chiến lược khuyến mãi)
public interface KhoanKhuyenMai {
double tinhGiaCuoiCung(double giaGoc); // Tính giá sau khi áp dụng khuyến mãi
}
2. Triển khai lớp chiến lược cụ thể
Chiến lược giảm giá khi đạt đủ số tiền
public class ChiTietGiamGiaTheoSoTien implements KhoanKhuyenMai {
private double soTienToiThieu;
private double soTienGiam;
public ChiTietGiamGiaTheoSoTien(double soTienToiThieu, double soTienGiam) {
this.soTienToiThieu = soTienToiThieu;
this.soTienGiam = soTienGiam;
}
@Override
public double tinhGiaCuoiCung(double giaGoc) {
if (giaGoc >= soTienToiThieu) {
return giaGoc - soTienGiam;
}
return giaGoc;
}
}
Chiến lược giảm giá theo tỷ lệ
public class ChiTietGiamGiaTheoTyLe implements KhoanKhuyenMai {
private double tyLeGiamGia;
public ChiTietGiamGiaTheoTyLe(double tyLeGiamGia) {
this.tyLeGiamGia = tyLeGiamGia;
}
@Override
public double tinhGiaCuoiCung(double giaGoc) {
return giaGoc * tyLeGiamGia;
}
}
Chiến lược không có khuyến mãi
public class ChiTietKhongKhuyenMai implements KhoanKhuyenMai {
@Override
public double tinhGiaCuoiCung(double giaGoc) {
return giaGoc;
}
}
3. Triển khai lớp ngữ cảnh (giỏ hàng)
public class GioHang {
private KhoanKhuyenMai khoanKhuyenMai;
public void setKhoanKhuyenMai(KhoanKhuyenMai khoanKhuyenMai) {
this.khoanKhuyenMai = khoanKhuyenMai;
}
public double thanhToan(double giaGoc) {
if (khoanKhuyenMai == null) {
khoanKhuyenMai = new ChiTietKhongKhuyenMai();
}
return khoanKhuyenMai.tinhGiaCuoiCung(giaGoc);
}
}
4. Client sử dụng mẫu chiến lược
public class DemoClient {
public static void main(String[] args) {
GioHang gioHang = new GioHang();
// Không có chương trình khuyến mãi
gioHang.setKhoanKhuyenMai(new ChiTietKhongKhuyenMai());
double gia1 = gioHang.thanhToan(100);
System.out.println("Không có khuyến mãi, giá cuối cùng: " + gia1);
// Giảm 20 khi đạt 100
gioHang.setKhoanKhuyenMai(new ChiTietGiamGiaTheoSoTien(100, 20));
double gia2 = gioHang.thanhToan(120);
System.out.println("Giảm 20 khi đạt 100, giá cuối cùng: " + gia2);
// Giảm 20%
gioHang.setKhoanKhuyenMai(new ChiTietGiamGiaTheoTyLe(0.8));
double gia3 = gioHang.thanhToan(150);
System.out.println("Giảm 20%, giá cuối cùng: " + gia3);
}
}
Kết quả đầu ra:
Không có khuyến mãi, giá cuối cùng: 100.0
Giảm 20 khi đạt 100, giá cuối cùng: 100.0
Giảm 20%, giá cuối cùng: 120.0
III. Nâng cao: Kết hợp mẫu Chiến lược với mẫu Nhà máy
Trong ứng dụng thực tế, để quản lý và lấy đối tượng chiến lược một cách thuận tiện hơn, chúng ta có thể kết hợp mẫu Chiến lược với mẫu Nhà máy.
public class NhaMayKhoanKhuyenMai {
public static KhoanKhuyenMai getKhoanKhuyenMai(String loaiKhoan) {
switch (loaiKhoan) {
case "giam_gia_so_tien":
return new ChiTietGiamGiaTheoSoTien(100, 20);
case "giam_gia_ty_le":
return new ChiTietGiamGiaTheoTyLe(0.8);
default:
return new ChiTietKhongKhuyenMai();
}
}
}
public class DemoClientNangCao {
public static void main(String[] args) {
GioHang gioHang = new GioHang();
// Sử dụng nhà máy để lấy chiến lược
gioHang.setKhoanKhuyenMai(NhaMayKhoanKhuyenMai.getKhoanKhuyenMai("giam_gia_so_tien"));
double gia = gioHang.thanhToan(120);
System.out.println("Sử dụng nhà máy để lấy chiến lược, giá cuối cùng: " + gia);
}
}
IV. Thực tiễn trong Framework và Mã nguồn: Mẫu Chiến lược
1. Giao diện Comparator của Java
Trong Java, giao diện Comparator là một ứng dụng điển hình của mẫu Chiến lược. Bằng cách triển khai các giao diện Comparator khác nhau, chúng ta có thể cung cấp các chiến lược sắp xếp khác nhau cho các nhu cầu sắp xếp khác nhau.
import java.util.Arrays;
import java.util.Comparator;
public class DemoComparator {
public static void main(String[] args) {
Integer[] so = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
// Sắp xếp tăng dần
Arrays.sort(so, Comparator.naturalOrder());
System.out.println("Sắp xếp tăng dần: " + Arrays.toString(so));
// Sắp xếp giảm dần
Arrays.sort(so, Comparator.reverseOrder());
System.out.println("Sắp xếp giảm dần: " + Arrays.toString(so));
}
}
2. ResourceLoader của Spring
Giao diện ResourceLoader và các lớp triển khai trong framework Spring cũng là ứng dụng của mẫu Chiến lược. Các lớp triển khai ResourceLoader khác nhau có thể cung cấp các chiến lược tải tài nguyên khác nhau dựa trên các loại tài nguyên khác nhau (như tệp, tài nguyên đường lớp, tài nguyên URL, v.v.).
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoSpringResourceLoader {
public static void main(String[] args) {
ResourceLoader resourceLoader = new ClassPathXmlApplicationContext();
Resource resource = resourceLoader.getResource("application.properties");
System.out.println("Tài nguyên có tồn tại không: " + resource.exists());
}
}
V. Hướng dẫn tránh sai lầm: 3 điểm chính khi sử dụng mẫu Chiến lược
1. Tránh có quá nhiều lớp chiến lược
Khi có quá nhiều lớp chiến lược, số lượng lớp sẽ tăng lên nhanh chóng, làm tăng độ phức tạp của hệ thống. Có thể cân nhắc hợp nhất một số lớp chiến lược tương tự nhau, hoặc sử dụng enum chiến lược để đơn giản hóa việc quản lý chiến lược.
2. Logic lựa chọn chiến lược
Khi sử dụng mẫu Chiến lược, cần xem xét cách lựa chọn chiến lược phù hợp. Có thể đóng gói logic lựa chọn chiến lược trong lớp ngữ cảnh, hoặc sử dụng mẫu Nhà máy để quản lý việc tạo và lựa chọn chiến lược.
3. Khả năng bảo trì của chiến lược
Mỗi lớp chiến lược nên duy trì tính độc lập và trách nhiệm duy nhất, tránh thêm quá nhiều logic nghiệp vụ vào lớp chiến lược. Đồng thời, cần cung cấp tài liệu và chú thích rõ ràng cho lớp chiến lược, thuận tiện cho việc bảo trì và mở rộng sau này.
VI. Tổng kết: Khi nào nên sử dụng mẫu Chiến lược?
| Tình huống áp dụng | Đặc điểm cốt lõi | Ví dụ điển hình |
|---|---|---|
| Chuyển đổi thuật toán động | Cần chọn thuật toán phù hợp một cách linh hoạt tại thời điểm chạy dựa trên các điều kiện khác nhau | Khuyến mãi thương mại điện tử, lựa chọn thuật toán sắp xếp |
| Tái sử dụng mã và khả năng bảo trì | Nhiều thuật toán có giao diện tương tự, cần tăng khả năng tái sử dụng và bảo trì mã | Thuật toán vẽ đồ họa, thuật toán mã hóa |
| Tránh sử dụng nhiều câu lệnh điều kiện | Tránh sử dụng nhiều câu lệnh if-else hoặc switch trong mã |
Kỹ năng nhân vật trong game, máy trạng thái |
Mẫu Chiến lược thông qua việc đóng gói các thuật toán thành các lớp chiến lược độc lập, thực hiện việc chuyển đổi thuật toán linh hoạt và tái sử dụng mã, là một mẫu thiết kế rất hữu ích. Bài viết tiếp theo chúng ta sẽ đi sâu vào mẫu phương thức mẫu, phân tích cách định nghĩa cấu trúc thuật toán và trì hoãn việc triển khai chi tiết, hãy cùng chờ đón!
Suy mở: Mẫu Chiến lược so với Mẫu Trạng thái
| Loại | Ý tưởng cốt lõi | Tình huống áp dụng |
|---|---|---|
| Mẫu Chiến lược | Định nghĩa một chuỗi các thuật toán có thể thay thế lẫn nhau, client chủ động chọn chiến lược | Chuyển đổi thuật toán linh hoạt, tái sử dụng mã |
| Mẫu Trạng thái | Hành vi của đối tượng phụ thuộc vào trạng thái của nó, sự thay đổi trạng thái dẫn đến sự thay đổi hành vi | Hành vi của đối tượng thay đổi theo trạng thái, máy trạng thái |
Hiểu sự khác biệt này sẽ giúp chúng ta chọn mẫu thiết kế phù hợp hơn trong các tình huống khác nhau.