Mẫu Factory
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 66 trên 87.
Factory Pattern là một mẫu thiết kế khởi tạo, giao việc tạo đối tượng cho một phương thức hoặc lớp riêng. Thay vì dùng trực tiếp new trong code, bạn yêu cầu một factory tạo đối tượng cho mình. Cách này đặc biệt hữu ích khi bạn có nhiều lớp liên quan cùng dùng chung một giao diện hoặc lớp cha.
Hãy xem xét một hệ thống thông báo có thể gửi email, SMS hoặc thông báo đẩy. Nếu không có factory, code của bạn cần biết mọi lớp cụ thể:
// Không dùng Factory, client phải biết tất cả các lớp (class) cụ thể
Notification notification;
if (type.equals("email")) {
notification = new EmailNotification();
} else if (type.equals("sms")) {
notification = new SMSNotification();
}Với mẫu Factory, bạn tập trung logic tạo đối tượng tại một nơi:
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
public void send(String message) {
System.out.println("Email: " + message);
}
}
public class SMSNotification implements Notification {
public void send(String message) {
System.out.println("SMS: " + message);
}
}
public class NotificationFactory {
public static Notification create(String type) {
if (type.equals("email")) {
return new EmailNotification();
} else if (type.equals("sms")) {
return new SMSNotification();
}
return null;
}
}Giờ đây, code phía client gọn hơn và không phụ thuộc vào các lớp cụ thể:
Notification notification = NotificationFactory.create("email");
notification.send("Hello!"); // Đầu ra: Email: Hello!Lợi ích chính là bạn chỉ cần cập nhật factory khi thêm loại thông báo mới, còn code phía client vẫn giữ nguyên. Cách này tuân theo nguyên tắc lập trình hướng đến interface, giúp hệ thống linh hoạt hơn và dễ mở rộng hơn.
Thử thách
DễHãy xây dựng một hệ thống vẽ hình bằng mẫu Factory! Thay vì tạo trực tiếp các đối tượng hình trong toàn bộ code, bạn sẽ tập trung logic tạo đối tượng vào một factory, nơi tạo ra nhiều loại hình khác nhau dựa trên một định danh chuỗi đơn giản.
Bạn sẽ sắp xếp code trong bốn tệp:
Shape.java: Định nghĩa một giao diện có tênShape, làm hợp đồng chung cho mọi hình. Giao diện của bạn cần khai báo một phương thức duy nhất làdraw(), phương thức này không trả về giá trị mà in thông tin về hình đang được vẽ.Shapes.java: Tạo ba lớp triển khai giao diệnShape:Circle- phương thứcdraw()cần inDrawing a CircleRectangle- phương thứcdraw()cần inDrawing a RectangleTriangle- phương thứcdraw()cần inDrawing a TriangleShapeFactory.java: Tạo lớp factory xử lý việc tạo hình.ShapeFactorycần có phương thức tĩnhcreateShape(String type)trả về mộtShape. Dựa trên tham số type:- Nếu type bằng
"circle", trả về mộtCirclemới - Nếu type bằng
"rectangle", trả về mộtRectanglemới - Nếu type bằng
"triangle", trả về mộtTrianglemới - Với mọi giá trị khác, trả về
null
- Nếu type bằng
Main.java: Kết hợp hệ thống factory của bạn! Bạn sẽ nhận hai đầu vào: hai loại hình (đều là String).Với mỗi đầu vào, dùng
ShapeFactoryđể tạo hình và lưu hình đó vào một biếnShape. Nếu factory trả về một hình hợp lệ (không phải null), hãy gọi phương thứcdraw()của hình đó. Nếu factory trả về null, hãy inUnknown shape: [type], trong đó [type] là đầu vào đã nhận.Xử lý lần lượt cả hai đầu vào, mỗi đầu vào tạo một dòng đầu ra riêng.
Bạn sẽ nhận hai đầu vào theo thứ tự: loại hình thứ nhất (String) và loại hình thứ hai (String).
Lưu ý rằng lớp Main của bạn không bao giờ dùng trực tiếp new Circle() hoặc new Rectangle(). Lớp này chỉ biết giao diện Shape và yêu cầu factory tạo các đối tượng. Đó chính là sức mạnh của mẫu Factory: code phía client không phụ thuộc vào các lớp cụ thể!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc hai loại hình
String type1 = scanner.nextLine();
String type2 = scanner.nextLine();
// TODO: Dùng ShapeFactory để tạo hình thứ nhất
// Nếu hình không phải null, gọi draw()
// Nếu hình là null, in ra "Unknown shape: [type]"
// TODO: Làm tương tự với hình thứ hai
}
}
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