Mẫu Strategy
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 69 trên 87.
Mẫu Strategy là một mẫu thiết kế hành vi cho phép bạn định nghĩa một nhóm thuật toán, đóng gói từng thuật toán trong lớp riêng và thay thế chúng cho nhau. Thay vì cố định một hành vi cụ thể trong code, bạn có thể thay đổi thuật toán khi chương trình đang chạy mà không cần sửa code sử dụng chúng.
Hãy hình dung một hệ thống thanh toán hỗ trợ nhiều phương thức thanh toán. Nếu không dùng Strategy Pattern, bạn sẽ phải xử lý những câu lệnh điều kiện rối rắm. Với mẫu này, mỗi phương thức thanh toán trở thành một chiến lược riêng:
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " via Credit Card");
}
}
class PayPalPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println("Paid " + amount + " via PayPal");
}
}Lớp context giữ tham chiếu đến một chiến lược và ủy quyền công việc cho chiến lược đó. Điểm mấu chốt là context không biết mình đang dùng chiến lược cụ thể nào. Nó chỉ biết giao diện:
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}Giờ bạn có thể thay đổi hành vi thanh toán một cách linh hoạt:
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100); // Đã thanh toán 100 bằng thẻ tín dụng
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(50); // Đã thanh toán 50 qua PayPalMẫu chiến lược (Strategy Pattern) phát huy hiệu quả khi bạn có nhiều cách thực hiện một hành động và muốn dễ dàng chuyển đổi giữa chúng. Để thêm phương thức thanh toán mới, bạn chỉ cần tạo một lớp mới triển khai PaymentStrategy: không cần thay đổi code hiện có.
Thử thách
DễHãy xây dựng một hệ thống định dạng văn bản bằng mẫu Strategy! Bạn sẽ tạo một hệ thống cho phép thay đổi các chiến lược định dạng khác nhau trong lúc chương trình chạy để biến đổi văn bản theo nhiều cách, rất phù hợp với ứng dụng cần hỗ trợ nhiều định dạng đầu ra.
Bạn sẽ tổ chức code trong bốn tệp:
TextFormatter.java: Định nghĩa giao diệnTextFormattermà tất cả chiến lược định dạng sẽ triển khai. Giao diện này cần khai báo một phương thức duy nhất làString format(String text), nhận một chuỗi và trả về phiên bản đã định dạng.Formatters.java: Tạo ba chiến lược cụ thể triển khai giao diệnTextFormatter:UpperCaseFormatter- chuyển văn bản thành chữ in hoa.LowerCaseFormatter- chuyển văn bản thành chữ thường.TitleCaseFormatter- viết hoa ký tự đầu tiên của mỗi từ và chuyển các ký tự còn lại thành chữ thường. Các từ được ngăn cách bằng dấu cách.TextEditor.java: Tạo lớp ngữ cảnh sử dụng một chiến lược định dạng.TextEditorcần có:Một trường private để lưu chiến lược
TextFormatterhiện tại.Một phương thức
setFormatter(TextFormatter formatter)để thay đổi chiến lược định dạng đang hoạt động.Một phương thức
publishText(String text)áp dụng bộ định dạng hiện tại cho văn bản và in raPublished: [formatted text]. Nếu chưa đặt bộ định dạng nào, phương thức sẽ in nguyên vănPublished: [original text].Main.java: Kết hợp hệ thống chiến lược của bạn! Bạn sẽ nhận hai đầu vào: văn bản cần định dạng (String) và kiểu định dạng (String:"upper","lower"hoặc"title").Tạo một
TextEditorvà gọipublishTextvới văn bản đầu vào trước khi đặt bộ định dạng nào, để thể hiện hành vi mặc định.Sau đó, dựa trên đầu vào kiểu định dạng, đặt chiến lược định dạng phù hợp và gọi lại
publishTextvới cùng văn bản để hiển thị kết quả đã định dạng.
Bạn sẽ nhận hai đầu vào theo thứ tự: văn bản cần định dạng (String) và kiểu định dạng (String).
Ví dụ, với đầu vào hello world và title, đầu ra sẽ là:
Published: hello world
Published: Hello WorldLưu ý rằng TextEditor không cần biết gì về logic định dạng cụ thể. Nó chỉ chuyển việc xử lý cho chiến lược hiện được đặt. Nhờ vậy, bạn có thể dễ dàng thêm kiểu định dạng mới mà không cần chỉnh sửa code của trình soạn thảo!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String text = scanner.nextLine();
String style = scanner.nextLine();
// TODO: Tạo một instance của TextEditor
// TODO: Gọi publishText với văn bản đầu vào (trước khi thiết lập bất kỳ bộ định dạng nào)
// TODO: Dựa vào kiểu định dạng đầu vào ("upper", "lower" hoặc "title"),
// thiết lập bộ định dạng phù hợp và gọi lại publishText
}
}Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)11Mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu BuilderMẫu ObserverMẫu Strategy3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online