bài viết ngẫu nhiên
Nguyên tắc Open/Closed được định nghĩa như sau:
Code nên mở rộng được nhưng hạn chế sửa đổi trực tiếp.
Nghe có vẻ “triết lý” nhưng thực chất rất thực tế:
Open (mở rộng): dễ dàng bổ sung tính năng mới.
Closed (đóng sửa đổi): không cần chạm tay vào code cũ để tránh gây lỗi domino.
Nói cách khác: code hôm nay của bạn phải “chuẩn bị tinh thần” để ngày mai có thể thêm cái mới mà không phá hỏng cái cũ.
Hãy tưởng tượng bạn có một cái ổ cắm điện.
Nếu mai bạn cần cắm laptop, bạn chỉ việc cắm thêm.
Nếu cần chuyển đổi sang phích cắm 3 chấu, bạn mua thêm đầu chuyển.
Bạn không cần đập tường đi dây lại mỗi khi mua thêm một cái máy mới.
Đó chính là tinh thần của OCP.
Giả sử bạn viết một hệ thống tính diện tích cho các hình học.
Cách viết vi phạm OCP:
class AreaCalculator {
calculate(shape: any) {
if (shape.type === 'rectangle') {
return shape.width * shape.height;
}
if (shape.type === 'circle') {
return Math.PI * shape.radius * shape.radius;
}
}
}
const calc = new AreaCalculator();
console.log(calc.calculate({ type: 'rectangle', width: 10, height: 20 }));
console.log(calc.calculate({ type: 'circle', radius: 5 }));
Vấn đề ở đây:
Nếu mai bạn muốn thêm Triangle, bạn phải sửa trực tiếp hàm calculate.
Sửa code cũ = nguy cơ làm hỏng những thứ vốn đang chạy ổn định.
Cải thiện theo OCP:
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
getArea() {
return this.width * this.height;
}
}
class Circle implements Shape {
constructor(private radius: number) {}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
class AreaCalculator {
calculate(shape: Shape) {
return shape.getArea();
}
}
// Sử dụng
const calc = new AreaCalculator();
console.log(calc.calculate(new Rectangle(10, 20)));
console.log(calc.calculate(new Circle(5)));
Bây giờ nếu muốn thêm Triangle, chỉ việc tạo một class mới implement Shape, không cần động vào code cũ nữa.
Bạn có thể thấy OCP khi dùng Dependency Injection. Ví dụ:
Hôm nay bạn xài MySQL.
Ngày mai chuyển sang Postgres.
Bạn chỉ cần implement một DatabaseService khác mà không sửa UserService.
interface Database {
save(data: any): void;
}
@Injectable()
class MySQLDatabase implements Database {
save(data: any) { console.log('Save to MySQL', data); }
}
@Injectable()
class UserService {
constructor(private readonly db: Database) {}
addUser(user: string) {
this.db.save(user);
}
}
Cái hay: bạn mở rộng bằng cách thêm implementation mới, không phải sửa code của UserService.
Bạn có thể áp dụng OCP khi render component. Ví dụ: một Button hỗ trợ nhiều loại style.
type ButtonType = 'primary' | 'secondary';
const Button: React.FC<{ type: ButtonType }> = ({ type, children }) => {
if (type === 'primary') return <button className="bg-blue-500">{children}</button>;
if (type === 'secondary') return <button className="bg-gray-500">{children}</button>;
return null;
};
Vi phạm OCP: muốn thêm danger button phải sửa thẳng trong component.
Refactor với OCP:
interface ButtonProps {
render: (children: React.ReactNode) => JSX.Element;
children: React.ReactNode;
}
const Button = ({ render, children }: ButtonProps) => render(children);
// Sử dụng
<Button render={(c) => <button className="bg-blue-500">{c}</button>}>Save</Button>
<Button render={(c) => <button className="bg-red-500">{c}</button>}>Delete</Button>
Giờ đây bạn có thể mở rộng vô hạn mà không cần sửa Button.
Khi hệ thống có khả năng thay đổi thường xuyên (ví dụ: thêm loại sản phẩm, loại hình thanh toán, loại user role…).
Khi bạn muốn giảm rủi ro bug từ việc sửa code cũ.
Khi làm việc theo nhóm: người khác có thể thêm tính năng mới mà không phá hỏng phần bạn đã viết.
Đừng lạm dụng OCP. Nếu bạn cố “chuẩn bị cho mọi khả năng mở rộng” thì code dễ thành over-engineering (quá phức tạp).
Quy tắc vàng:
Nếu chắc chắn sẽ mở rộng → áp dụng OCP.
Nếu chưa biết → code đơn giản trước, refactor sau.
Nguyên tắc Open/Closed không bắt bạn phải đoán trước tương lai, mà giúp code của bạn linh hoạt trước sự thay đổi. Hãy nhớ: thêm thì dễ, sửa thì ít. Và nếu lần sau bạn thấy phải lục tung code cũ chỉ để thêm một tính năng nhỏ, hãy tự hỏi: Liệu mình đã áp dụng OCP đúng cách chưa?