Lập trình · Mẫu thiết kế
Mẫu Abstract Factory giải thích kèm ví dụ
Trả lời nhanh
Abstract Factory là mẫu thiết kế khởi tạo cung cấp một interface để tạo các họ đối tượng liên quan mà không chỉ rõ lớp cụ thể của chúng. Mã client nhận một đối tượng factory, như LightThemeFactory hoặc DarkThemeFactory, và yêu cầu nó tạo từng sản phẩm (một nút bấm, một hộp thoại). Vì một factory tạo cả họ, các sản phẩm luôn khớp với nhau, và bạn đổi họ bằng cách truyền một factory khác.
Mục đích
Cung cấp một interface để tạo các họ đối tượng liên quan hoặc phụ thuộc nhau mà không chỉ rõ lớp cụ thể của chúng. Abstract Factory là một trong năm mẫu khởi tạo trong cuốn sách Gang of Four (GoF), nơi nó còn được gọi là Kit.
Vấn đề nó giải quyết
Hãy hình dung một ứng dụng báo cáo có thể xuất kết quả cho hai đích: trang web và terminal văn bản thuần. Mỗi đích cần phiên bản riêng của nhiều thành phần: tiêu đề, bảng và hộp cảnh báo. Tiêu đề web không được trộn với bảng của terminal, nếu không đầu ra sẽ hỏng.
Nếu mã báo cáo gọi trực tiếp new HtmlHeading() và new TextTable(), nó bị buộc vào các lớp cụ thể, lựa chọn đích bị rải trên nhiều dòng, và không gì ngăn ai đó trộn lẫn các họ. Abstract Factory đặt mọi việc tạo của một họ sau một đối tượng, nên lựa chọn được đưa ra ở đúng một chỗ.
Cấu trúc và các thành phần tham gia
| Thành phần | Trách nhiệm | Trong ví dụ |
|---|---|---|
| AbstractFactory | Khai báo một phương thức tạo cho mỗi kiểu sản phẩm. | UiFactory |
| ConcreteFactory | Triển khai các phương thức tạo cho một họ. | HtmlFactory, TextFactory |
| AbstractProduct | Interface cho một loại sản phẩm. | Heading, Alert |
| ConcreteProduct | Một sản phẩm của một họ cụ thể. | HtmlHeading, TextAlert |
| Client | Chỉ dùng abstract factory và các interface sản phẩm trừu tượng. | renderReport |
Triển khai bằng TypeScript
interface Heading { render(text: string): string; }
interface Alert { render(text: string): string; }
interface UiFactory {
createHeading(): Heading;
createAlert(): Alert;
}
class HtmlFactory implements UiFactory {
createHeading(): Heading { return { render: (t) => `<h1>${t}</h1>` }; }
createAlert(): Alert { return { render: (t) => `<div class="alert">${t}</div>` }; }
}
class TextFactory implements UiFactory {
createHeading(): Heading { return { render: (t) => `${t}\n${"=".repeat(t.length)}` }; }
createAlert(): Alert { return { render: (t) => `[!] ${t}` }; }
}
// The client knows only the interfaces.
function renderReport(ui: UiFactory): string {
const heading = ui.createHeading();
const alert = ui.createAlert();
return [heading.render("Sales"), alert.render("2 orders failed")].join("\n");
}
const target: string = "text"; // e.g. from a command-line flag
const factory: UiFactory = target === "html" ? new HtmlFactory() : new TextFactory();
console.log(renderReport(factory));
// Sales
// =====
// [!] 2 orders failedNơi duy nhất nêu tên một lớp cụ thể là dòng chọn factory. renderReport chạy được với mọi họ, và nó không thể vô tình trộn mảnh HTML với mảnh văn bản, vì nó lấy toàn bộ sản phẩm từ cùng một factory.
Trong chương trình thật, các sản phẩm thường là lớp có nhiều phương thức hơn. Object literal giúp ví dụ ngắn gọn; cấu trúc vẫn như vậy.
Khi nào nên dùng, và khi nào không
Dùng mẫu Abstract Factory khi:
- Hệ thống phải làm việc với nhiều họ sản phẩm liên quan, và sản phẩm của các họ khác nhau không được kết hợp (giao diện chủ đề UI, widget của hệ điều hành, driver cơ sở dữ liệu với các đối tượng kết nối và lệnh khớp nhau).
- Bạn muốn chọn họ một lần, lúc cấu hình, và giữ phần còn lại của mã độc lập với nó.
- Bạn muốn kiểm thử cung cấp một họ đối tượng giả thông qua một factory.
Tránh nó khi:
- Chỉ có một họ.
newtrực tiếp, hoặc một hàm factory, đơn giản hơn. - Các kiểu sản phẩm thay đổi thường xuyên. Thêm một loại sản phẩm mới, như
createTable(), buộc bạn đổi interface factory và mọi factory cụ thể. Đây là điểm yếu chính mà cuốn sách GoF nêu.
Abstract Factory so với Factory Method
| Khía cạnh | Factory Method | Abstract Factory |
|---|---|---|
| Tạo ra | Một sản phẩm | Một họ sản phẩm liên quan |
| Cơ chế | Một lớp con ghi đè một phương thức tạo | Client nhận một đối tượng factory với nhiều phương thức tạo |
| Dựa trên | Kế thừa | Composition |
| Dễ thêm | Một biến thể sản phẩm mới (lớp con mới) | Một họ mới (lớp factory mới) |
| Khó thêm | Ít; mỗi creator độc lập | Một kiểu sản phẩm mới (mọi factory đều phải đổi) |
Hai mẫu này thường được kết hợp: mỗi phương thức tạo của một factory cụ thể tự nó là một factory method.
Ứng dụng thực tế
- API XML của Java:
DocumentBuilderFactoryvàTransformerFactorylà các lớp abstract.newInstance()tìm một cách triển khai cụ thể, còn mã dùng nó chỉ phụ thuộc vào các kiểu trừu tượng. - API JDBC của Java hoạt động tương tự: driver cơ sở dữ liệu cung cấp các cách triển khai
Connection,StatementvàResultSetkhớp nhau, còn mã ứng dụng chỉ dùng các interface. - Ví dụ của chính cuốn sách GoF là bộ công cụ giao diện người dùng hỗ trợ nhiều chuẩn look-and-feel, với một factory cho mỗi chuẩn tạo ra thanh cuộn, cửa sổ và nút bấm khớp nhau.
Các mẫu liên quan
- Mẫu Factory Method: các factory cụ thể thường triển khai phương thức tạo của mình dưới dạng factory method.
- Mẫu Prototype: một factory có thể được cấu hình với các đối tượng nguyên mẫu và tạo sản phẩm bằng cách sao chép chúng, tránh phải có một lớp con factory cho mỗi họ.
- Mẫu Singleton: một ứng dụng thường chỉ cần một thể hiện của mỗi factory cụ thể.
- Mẫu Builder: cũng ẩn việc xây dựng, nhưng xây một đối tượng phức tạp từng bước, còn Abstract Factory trả về sản phẩm hoàn chỉnh ngay lập tức.
- Mẫu Bridge: một abstract factory có thể tạo và cấu hình phía triển khai của một bridge.
Câu hỏi thường gặp
Họ sản phẩm là gì?
Một tập các đối tượng được thiết kế để dùng cùng nhau, như nút bấm, menu và hộp thoại của một chủ đề UI. Trộn sản phẩm của các họ khác nhau sẽ cho kết quả không nhất quán hoặc hỏng.
Làm sao thêm một kiểu sản phẩm mới vào Abstract Factory?
Bạn thêm một phương thức tạo vào interface factory và triển khai nó trong mọi factory cụ thể. Vì vậy mẫu này phù hợp nhất khi tập các kiểu sản phẩm ổn định.
Abstract Factory có giống tiêm phụ thuộc không?
Không, nhưng chúng phối hợp tốt. Tiêm phụ thuộc cung cấp một đối tượng từ bên ngoài; Abstract Factory là một loại đối tượng bạn có thể tiêm khi client cần tự tạo nhiều đối tượng liên quan.
Abstract Factory có thể là một hàm thay vì lớp không?
Có. Trong TypeScript, một factory có thể là một đối tượng thuần chứa các hàm tạo thỏa mãn interface factory. Mẫu này nói về interface và các họ, không phải về lớp.