Design Patterns — 23 Patterns Cổ Điển Cho Developer Hiện Đại
Các design pattern Gang of Four quan trọng: Singleton, Factory, Observer, Strategy và ứng dụng trong code thực tế.
Design patterns là giải pháp cho các vấn đề lặp lại trong thiết kế phần mềm. Không phải code mẫu — mà là template giải quyết vấn đề.
Singleton — Chỉ Một Instance#
Đảm bảo class chỉ có một instance duy nhất.
class Database {
private static instance: Database;
private constructor() {} // private constructor
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
query(sql: string) {
console.log(`Executing: ${sql}`);
}
}
// Usage
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // truetypescriptDùng khi: Connection pool, logger, config manager.
Factory Method — Tạo Object Mà Không Cần Biết Class#
interface Notification {
send(message: string): void;
}
class EmailNotification implements Notification {
send(message: string) {
console.log(`Email: ${message}`);
}
}
class SMSNotification implements Notification {
send(message: string) {
console.log(`SMS: ${message}`);
}
}
class NotificationFactory {
static create(type: 'email' | 'sms'): Notification {
switch (type) {
case 'email': return new EmailNotification();
case 'sms': return new SMSNotification();
}
}
}
// Usage
const notifier = NotificationFactory.create('email');
notifier.send('Hello!');typescriptDùng khi: Logic tạo object phức tạp, muốn giấu implementation.
Observer — Publish/Subscribe#
Khi object thay đổi, tất cả dependent object được thông báo.
interface Observer {
update(data: any): void;
}
class Subject {
private observers: Observer[] = [];
subscribe(observer: Observer) {
this.observers.push(observer);
}
unsubscribe(observer: Observer) {
this.observers = this.observers.filter(o => o !== observer);
}
notify(data: any) {
this.observers.forEach(o => o.update(data));
}
}
// Usage
class Logger implements Observer {
update(data: any) {
console.log('Log:', data);
}
}
class EmailService implements Observer {
update(data: any) {
console.log('Sending email for:', data);
}
}
const subject = new Subject();
subject.subscribe(new Logger());
subject.subscribe(new EmailService());
subject.notify({ event: 'user_created', id: 1 });typescriptDùng khi: Event system, real-time update, React 상태 관리 (RxJS, Zustand).
Strategy — Algorithm Có Thể Swap#
interface SortStrategy {
sort(data: number[]): number[];
}
class BubbleSort implements SortStrategy {
sort(data: number[]) {
console.log('Bubble sort');
return data.sort((a, b) => a - b);
}
}
class QuickSort implements SortStrategy {
sort(data: number[]) {
console.log('Quick sort');
return data.sort((a, b) => a - b);
}
}
class Sorter {
constructor(private strategy: SortStrategy) {}
setStrategy(strategy: SortStrategy) {
this.strategy = strategy;
}
sort(data: number[]) {
return this.strategy.sort(data);
}
}
// Usage
const sorter = new Sorter(new BubbleSort());
sorter.sort([3, 1, 2]);
sorter.setStrategy(new QuickSort());
sorter.sort([3, 1, 2]);typescriptDùng khi: Nhiều cách xử lý, muốn chọn linh hoạt (sort, compression, authentication).
Decorator — Thêm Chức Năng Mà Không Sửa Class#
interface Coffee {
cost(): number;
description(): string;
}
class SimpleCoffee implements Coffee {
cost() { return 10; }
description() { return 'Coffee'; }
}
class MilkDecorator implements Coffee {
constructor(private coffee: Coffee) {}
cost() { return this.coffee.cost() + 3; }
description() { return this.coffee.description() + ' + Milk'; }
}
class SugarDecorator implements Coffee {
constructor(private coffee: Coffee) {}
cost() { return this.coffee.cost() + 1; }
description() { return this.coffee.description() + ' + Sugar'; }
}
// Usage
let coffee: Coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.description()); // Coffee + Milk + Sugar
console.log(coffee.cost()); // 14typescriptDùng khi: Cần thêm chức năng linh hoạt, tránh class explosion (quá nhiều subclass).
Adapter — Kết Nối Hai Interface Khác Nhau#
class OldAPI {
getUser(id: number): string {
return `User{id=${id}}`;
}
}
interface NewAPI {
fetchUser(id: number): { id: number; name: string };
}
class UserAdapter implements NewAPI {
constructor(private oldApi: OldAPI) {}
fetchUser(id: number) {
const data = this.oldApi.getUser(id);
return { id, name: data };
}
}
// Usage
const adapter = new UserAdapter(new OldAPI());
const user = adapter.fetchUser(1);typescriptDùng khi: Legacy code integration, third-party library wrapper.
Repository — Abstraction Cho Data Layer#
interface UserRepository {
findById(id: number): Promise<User | null>;
save(user: User): Promise<void>;
delete(id: number): Promise<void>;
}
class PostgresUserRepository implements UserRepository {
async findById(id: number) {
return db.query('SELECT * FROM users WHERE id = $1', [id]);
}
async save(user: User) {
return db.query('INSERT INTO users VALUES ($1, $2)', [user.id, user.name]);
}
async delete(id: number) {
return db.query('DELETE FROM users WHERE id = $1', [id]);
}
}
class UserService {
constructor(private repo: UserRepository) {}
async getUser(id: number) {
const user = await this.repo.findById(id);
if (!user) throw new Error('User not found');
return user;
}
}
// Dễ dàng đổi database
const repo = new PostgresUserRepository();
// const repo = new MongoUserRepository();
const service = new UserService(repo);typescriptDùng khi: Dependency injection, dễ test (mock repo), dễ đổi database.
Dependency Injection — Đừng New, Hãy Nhận#
// ❌ Tệ — hardcode dependency
class OrderService {
private logger = new ConsoleLogger();
private email = new SmtpEmailService();
}
// ✅ Tốt — inject dependency
class OrderService {
constructor(
private logger: Logger,
private email: EmailService,
) {}
}
// DI Container (ví dụ đơn giản)
class Container {
private services = new Map<string, any>();
register(name: string, instance: any) {
this.services.set(name, instance);
}
resolve<T>(name: string): T {
return this.services.get(name);
}
}
const container = new Container();
container.register('logger', new ConsoleLogger());
container.register('email', new SmtpEmailService());
const orderService = new OrderService(
container.resolve('logger'),
container.resolve('email'),
);typescriptKhi Nào Dùng Pattern?#
- Không dùng pattern vì nó “sạch” — pattern có chi phí
- Pattern giải quyết vấn đề cụ thể — nếu không có vấn đề, đừng pattern
- Code đơn giản > Code pattern — pattern là công cụ, không phải mục tiêu
Kết Luận#
23 patterns trong GoF, bạn chỉ thực sự cần khoảng 5-7:
- Singleton, Factory, Repository — hầu như project nào cũng dùng
- Observer, Strategy — event handling, algorithm selection
- Adapter, Decorator — integration, mở rộng
Học pattern để có từ vựng giao tiếp với đồng nghiệp: “Chỗ này dùng Observer”, “Refactor theo Strategy pattern”. Quan trọng nhất: hiểu vấn đề pattern giải quyết, không phải nhớ code mẫu.