1. Tổng quan — Creational Patterns
Creational patterns tập trung vào cách tạo object. Chúng giúp:
- Giấu logic phức tạp khi khởi tạo.
- Cho phép tạo object thuộc family liên quan mà không khóa cứng concrete class.
- Kiểm soát số lượng instance.
- Tái sử dụng đối tượng có sẵn (clone) thay vì tạo mới đắt đỏ.
5 pattern trong nhóm:
| Pattern | Một câu | Khi nào dùng |
|---|---|---|
| Factory Method | Subclass quyết định class nào instantiate | Có nhiều biến thể, khởi tạo phụ thuộc context |
| Abstract Factory | Tạo "family" object liên quan | Cần consistency giữa nhóm object (vd: UI Mac/Win) |
| Builder | Xây object phức tạp từng bước | Object có nhiều tham số, nhiều bước khởi tạo |
| Prototype | Clone object có sẵn | Khởi tạo đắt, có "template" |
| Singleton | Đúng 1 instance toàn app | Hiếm — thường có cách tốt hơn |
2. Factory Method
Factory Method
Định nghĩa interface để tạo object, nhưng để subclass quyết định class cụ thể nào instantiate. Cho phép class trì hoãn việc khởi tạo cho subclass.
2.1. Vấn đề
// ❌ Code phải sửa khi thêm shipping provider mới:
class OrderProcessor {
ship(order: Order, country: string) {
let provider: ShippingProvider;
if (country === 'VN') provider = new VietnamPost();
else if (country === 'US') provider = new FedEx();
else if (country === 'JP') provider = new JapanPost();
// Mỗi provider mới → sửa class này
provider.ship(order);
}
}
2.2. Cấu trúc UML
2.3. Implementation
// Product
interface ShippingProvider {
ship(order: Order): void;
}
class VietnamPost implements ShippingProvider { ship(o: Order) {} }
class FedEx implements ShippingProvider { ship(o: Order) {} }
class JapanPost implements ShippingProvider { ship(o: Order) {} }
// Creator
abstract class OrderProcessor {
abstract createProvider(): ShippingProvider; // factory method
ship(order: Order) {
const provider = this.createProvider();
provider.ship(order);
}
}
// Concrete creators
class VnOrderProcessor extends OrderProcessor {
createProvider() { return new VietnamPost(); }
}
class UsOrderProcessor extends OrderProcessor {
createProvider() { return new FedEx(); }
}
2.4. Cách dùng đơn giản hơn — static factory method
class ShippingProviderFactory {
static forCountry(country: string): ShippingProvider {
switch (country) {
case 'VN': return new VietnamPost();
case 'US': return new FedEx();
case 'JP': return new JapanPost();
default: return new InternationalCourier();
}
}
}
// Client:
const provider = ShippingProviderFactory.forCountry(order.country);
provider.ship(order);
"Static factory method" thực ra không phải GoF Factory Method gốc, mà là biến thể đơn giản. Trong thực tế đa số dev gọi cả hai là "Factory".
2.5. Khi NÀO dùng
- Có nhiều biến thể của 1 product, chọn theo context.
- Logic khởi tạo phức tạp (nhiều argument, validate, lookup).
- Muốn cache instance, hoặc có pool.
- Bạn cần hide concrete class khỏi client.
2.6. Khi KHÔNG cần
- Chỉ có 1 cách tạo, không có biến thể: dùng
newđủ rồi. - Constructor đã đủ — đừng tạo factory chỉ để wrap
new.
3. Abstract Factory
Abstract Factory
Cung cấp interface để tạo các family object liên quan mà không cần chỉ định class cụ thể.
3.1. Cấu trúc
// Abstract products
interface Button { render(): void; }
interface Checkbox { render(): void; }
// Concrete products — 2 family
class MacButton implements Button { render() { console.log('Mac button'); } }
class MacCheckbox implements Checkbox { render() { console.log('Mac checkbox'); } }
class WinButton implements Button { render() { console.log('Windows button'); } }
class WinCheckbox implements Checkbox { render() { console.log('Windows checkbox'); } }
// Abstract factory
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// Concrete factories
class MacFactory implements UIFactory {
createButton() { return new MacButton(); }
createCheckbox() { return new MacCheckbox(); }
}
class WinFactory implements UIFactory {
createButton() { return new WinButton(); }
createCheckbox() { return new WinCheckbox(); }
}
// Client — không biết đang ở Mac hay Win
class App {
constructor(private factory: UIFactory) {}
render() {
this.factory.createButton().render();
this.factory.createCheckbox().render();
}
}
// Composition root chọn factory:
const factory: UIFactory = process.platform === 'darwin' ? new MacFactory() : new WinFactory();
new App(factory).render();
3.2. Khác Factory Method ở đâu?
| Factory Method | Abstract Factory | |
|---|---|---|
| Tạo | Một product | Family nhiều product |
| Cách | Method (override trong subclass) | Object factory chứa nhiều method |
| Mức trừu tượng | Method-level | Class/Object-level |
3.3. Use cases thực tế
- Multi-DB driver:
PostgresFactorytạo connection + transaction + query builder Postgres-style;MySQLFactorytương tự cho MySQL. - Theme system:
DarkThemetạo button/input/card với màu tối;LightThemevới màu sáng. - Cross-platform UI library (cũ): React Native, Flutter dùng kiểu này nội bộ.
4. Builder
Builder
Tách quá trình xây object phức tạp ra khỏi class chính, cho phép tạo nhiều biểu diễn khác nhau bằng cùng quá trình xây.
4.1. Vấn đề: Telescoping Constructor
class Pizza {
constructor(
size: number,
cheese: boolean,
pepperoni: boolean,
mushrooms: boolean,
onions: boolean,
olives: boolean,
extraSauce: boolean,
glutenFree: boolean,
) {}
}
// ❌ Khó đọc:
new Pizza(12, true, false, true, false, false, true, false);
// ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
// size ? ? ? ? ? ? ?
4.2. Cách giải Builder
class Pizza {
// Private constructor — chỉ Builder gọi
private constructor(
public readonly size: number,
public readonly toppings: ReadonlyArray<string>,
public readonly extraSauce: boolean,
public readonly glutenFree: boolean,
) {}
static builder(size: number) {
return new PizzaBuilder(size);
}
}
class PizzaBuilder {
private toppings: string[] = [];
private extraSauce = false;
private glutenFree = false;
constructor(private size: number) {}
addTopping(t: string): this {
this.toppings.push(t);
return this;
}
withExtraSauce(): this { this.extraSauce = true; return this; }
glutenFreeOption(): this { this.glutenFree = true; return this; }
build(): Pizza {
if (this.size < 8 || this.size > 16) throw new Error('invalid size');
return new Pizza(this.size, [...this.toppings], this.extraSauce, this.glutenFree);
}
}
// ✓ Tự nhiên như nói chuyện:
const pizza = Pizza.builder(12)
.addTopping('cheese')
.addTopping('pepperoni')
.addTopping('mushrooms')
.withExtraSauce()
.build();
4.3. Builder vs Plain Object Argument
Trong TypeScript/JS, "named parameters" qua object thường thay được Builder cho case đơn giản:
// Đơn giản hơn cho 5-7 field:
new Pizza({
size: 12,
toppings: ['cheese', 'pepperoni'],
extraSauce: true,
glutenFree: false,
});
Builder thắng khi:
- Cần validate giữa các bước xây.
- Có method composite (vd:
withMargheritaPreset()set nhiều field). - Cần nhiều biểu diễn xuất ra (build → Pizza, build → PizzaOrder, build → ToppingsList...).
- Object thực sự phức tạp (SQL query, HTTP request có nhiều tầng nested).
4.4. Director (variant)
GoF gốc có thêm "Director" gói các bước build phổ biến:
class PizzaDirector {
static margherita(builder: PizzaBuilder) {
return builder.addTopping('mozzarella').addTopping('basil').build();
}
static pepperoniDeluxe(builder: PizzaBuilder) {
return builder.addTopping('cheese').addTopping('pepperoni').addTopping('extra-pepperoni').build();
}
}
Hiếm gặp trong code thực tế — preset thường là static method của Builder.
4.5. Use case kinh điển
StringBuildertrong Java — nối string hiệu quả.- SQL Query Builder (Knex.js, jOOQ).
- HTTP Request Builder (axios, OkHttp).
- UI Form Builder.
5. Prototype
Prototype
Tạo object mới bằng cách clone một object có sẵn (prototype) thay vì gọi constructor.
5.1. Cấu trúc
interface Cloneable<T> {
clone(): T;
}
class Document implements Cloneable<Document> {
constructor(
public title: string,
public content: string,
public tags: string[],
public metadata: Record<string, any>,
) {}
clone(): Document {
return new Document(
this.title,
this.content,
[...this.tags], // shallow copy array
JSON.parse(JSON.stringify(this.metadata)), // deep clone object
);
}
}
// Sử dụng:
const template = new Document('Report', 'Default content...', ['draft'], { author: 'system' });
const doc1 = template.clone();
doc1.title = 'Q1 Report';
const doc2 = template.clone();
doc2.title = 'Q2 Report';
5.2. Deep clone — pitfall
Shallow clone copy reference, deep clone copy đệ quy. JSON.parse(JSON.stringify(x)) đơn giản nhưng:
- Mất Date, RegExp, Map, Set, Function (chuyển thành object/null/undefined).
- Vỡ với circular reference.
- Mất prototype chain.
Cleaner: structuredClone() (browser/Node 17+):
const copy = structuredClone(original);
// Hỗ trợ Date, Map, Set, ArrayBuffer, circular reference
5.3. JavaScript prototype-based — Prototype "miễn phí"
JS có Object.create tạo object mới với prototype được chỉ định:
const carPrototype = {
drive() { console.log(`${this.brand} is driving`); }
};
const myCar = Object.create(carPrototype);
myCar.brand = 'Toyota';
myCar.drive(); // "Toyota is driving"
5.4. Use case
- Game: spawn 1000 enemy "giống" template thay vì tạo từ database.
- Document templates (Word, Excel templates).
- Object cache: load 1 lần, clone cho user khác.
- Configuration "preset" + customize.
6. Singleton
Singleton
Đảm bảo 1 class chỉ có đúng 1 instance, cung cấp global access point đến nó.
6.1. Implementation cơ bản
class Logger {
private static _instance: Logger | null = null;
private constructor() {} // private → ai đó new sẽ lỗi
static getInstance(): Logger {
if (!this._instance) {
this._instance = new Logger();
}
return this._instance;
}
log(msg: string) {
console.log(`[${new Date().toISOString()}] ${msg}`);
}
}
// Dùng:
Logger.getInstance().log('hello');
6.2. Thread-safe (Java)
// Bill Pugh idiom — lazy + thread-safe + no synchronized cost
public class Logger {
private Logger() {}
private static class Holder {
private static final Logger INSTANCE = new Logger();
}
public static Logger getInstance() {
return Holder.INSTANCE;
}
}
// Hoặc dùng enum (Bloch khuyên):
public enum Logger {
INSTANCE;
public void log(String msg) { /* ... */ }
}
6.3. Vì sao Singleton bị "ghét"?
- Hidden dependency — gọi
Logger.getInstance()bên trong method = dependency ẩn, không thấy ở constructor signature. - Global state — test khó: 1 test set state → test khác bị ảnh hưởng.
- Khó parallel — 2 thread cùng set config → race condition.
- Khóa cứng implementation — không thể swap với mock dễ dàng.
6.4. Thay thế: DI + 1 instance ở composition root
// Composition root tạo 1 instance:
const logger = new Logger(/* config */);
// Inject mọi nơi:
const userService = new UserService(logger, ...);
const orderService = new OrderService(logger, ...);
// Test: tạo mock dễ dàng
const mockLogger = { log: jest.fn() };
const svc = new UserService(mockLogger, ...);
Một instance qua DI có cùng hiệu ứng "1 instance app" mà không có hidden global state. Đây là cách hiện đại.
6.5. Khi Singleton thực sự hữu ích
- Resource thực sự duy nhất ở OS level: file system handle, hardware driver.
- Cache với invariant strict: phải có đúng 1 cache để consistency.
- Thư viện không có DI container (script đơn giản, prototype).
7. Pattern hiện đại thay thế Creational
7.1. Dependency Injection thay Factory
DI container biết cách wire instance — ít cần Factory class viết tay.
// Thay vì:
class ShippingFactory {
create(country: string): ShippingProvider { /* switch */ }
}
// DI register:
container.register('VN', VietnamPost);
container.register('US', FedEx);
// Resolve:
const provider = container.resolve(order.country);
7.2. Tagged Union / Discriminated Union thay Polymorphism Factory
FP-style cho data variant:
type Shape =
| { type: 'circle'; radius: number }
| { type: 'rectangle'; width: number; height: number }
| { type: 'triangle'; base: number; height: number };
function area(s: Shape): number {
switch (s.type) {
case 'circle': return Math.PI * s.radius ** 2;
case 'rectangle': return s.width * s.height;
case 'triangle': return s.base * s.height / 2;
}
}
Ưu: đơn giản, type-safe, không cần class. Nhược: thêm variant phải sửa area (vi phạm OCP một phần). Trade-off — chọn tùy bối cảnh.
7.3. Object literals và Configuration
Cho object đơn giản, đừng dùng Builder — dùng plain object:
interface PizzaConfig {
size: number;
toppings: string[];
extraSauce?: boolean;
glutenFree?: boolean;
}
function makePizza(c: PizzaConfig): Pizza {
return new Pizza(c.size, c.toppings, c.extraSauce ?? false, c.glutenFree ?? false);
}
8. Bài tập
- Implement Factory Method cho hệ thống thanh toán:
PaymentProcessorabstract với subclassStripeProcessor,VnPayProcessor,MomoProcessor. Mỗi processor tạoPaymentGatewayriêng. - Abstract Factory cho theme:
LightThemevàDarkThememỗi cái tạoButton,Input,Cardtương ứng. App dùng theme bất kỳ. - Builder cho HTTP request:
const req = HttpRequest.builder() .url('https://api.example.com/users') .method('POST') .header('Authorization', 'Bearer xxx') .json({ name: 'Alice' }) .timeout(5000) .build(); - Prototype: tạo class
GameEnemyvới template "Goblin" (hp=50, atk=10). Spawn 100 goblin từ template, mỗi cái có thể customize hp/atk khác nhau mà không ảnh hưởng template. - Singleton: cài đặt
AppConfigsingleton. Sau đó refactor để dùng DI thay thế. So sánh trải nghiệm khi viết test. - Phân biệt Factory Method vs Abstract Factory bằng 1 ví dụ thực tế.
- Cho code Pizza ở mục 4.2 — tạo preset method
builder.margherita()set sẵn topping mozzarella + basil. Khi nào nên có preset trên Builder?
9. Quiz
Quiz cuối Chương 4
Factory Method giải vấn đề chính:
new ConcreteX(). Client gọi factory.create() hoặc creator.factoryMethod() — không biết concrete nào được tạo. Cho phép thêm/bớt biến thể mà không sửa client code (Open/Closed).Khác biệt cốt lõi giữa Factory Method và Abstract Factory:
Builder pattern phù hợp nhất khi:
Trong TypeScript, alternative đơn giản cho Builder thường là:
Prototype pattern dùng khi:
structuredClone built-in cho deep clone.Singleton bị chỉ trích vì:
Logger.getInstance() trong method = dependency không hiện ở constructor signature. Test khó (state shared, hard to mock). Hiện đại: dùng DI, đăng ký 1 instance ở composition root → cùng hiệu quả, không nhược điểm.Modern alternative thay Factory class trong nhiều case:
JSON.parse(JSON.stringify(x)) để clone deep KHÔNG hỗ trợ:
{}, RegExp thành {}, Function biến mất, circular ref throw. Modern: structuredClone() (browser/Node 17+) hỗ trợ tất cả + circular.Hoàn thành Chương 4 (5/23 patterns). Tiếp theo: Chương 5 — Structural Patterns (7 mẫu) →