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ế:
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.
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:
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ụ:
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.
Đừ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:
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?