Giới thiệu
Đây là nguyên lý thứ hai trong SOLID, Open closed principle ở bài viết này chúng ta sẽ còn nhau tìm hiểu về nguyên lý này.
Phát biểu: Một software entities(class, modules, functions, etc..) không nên chỉnh sửa thay vào đó hãy mở rộng chúng.
Giải thích nguyên lý
Trong bài viết này mình hay dùng từ module, để dễ hiểu thì các bạn cứ xem module là class nhé.
Theo nguyên lý này thì một module cần đáp ứng 2 điều kiện sau:
- Dễ mở rộng: Có thể dễ dàng nâng cấp, mở rộng và thêm tính năng mới.
- Hạn chế việc sửa đổi: Hạn chế hoặc cấm việc sữa đổi mã nguồn sẵn có trong module.
Hai yêu cầu trên nghe có vẽ mâu thuẫn vì khi mình muốn thêm tính năng thì tất nhiên là mình cần viết thêm code và đôi khi cần sữa đổi lại code cũ để áp dụng cho cả tính năng mới của mình và vẫn hoạt động tốt với code cũ. Thế nhưng đối với nguyên lý này thì việc sữa đổi code cũ khi thêm một tính năng mới là cực kỳ hạn chế.
Có thể nói khẩu súng là một hình mẫu áp dụng nguyên lý open closed principle tốt rất tốt. Để bắn được xa hơn thì bạn chỉ cần lắp thêm ống kính, để không gây tiếng động thì gắn nòng giảm thanh, để tăng số lượng đạn thì chỉ cần gắn thêm băng đạn phụ, cận chiếm thì có thể gắn cờ lê. Các thao tác trên đều không ảnh hưởng đến những gì đang có trên khẩu súng, một module cũng nên có thiết kế như vậy để tăng tính mở rộng và tái sử dụng.
Mình xét ví dụ nhỏ nhé.
Chúng ta có class Rectangle
public class Rectangle { private int width; private int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } }
Bây giờ mình muốn tính tổng diện tích các hình chữ nhật
public class AreaCalculator { public double Area(Rectangle[] rectangles) { double area = 0; for (Rectangle rectangle : rectangles) { area += rectangle.getWidth() * rectangle.getHeight(); } return area; } }
Mình chỉ cần loop trên mảng input và cộng dồn diện tích lại thôi, ok tới đây vẫn ổn, không có vấn đề gì. Sau đó application mình cần mở rộng, mình muốn tính thêm cả hình tròn nữa. Chuyện nhỏ mình chỉ cần làm đơn giản như sau
public class AreaCalculator { public double Area(Object[] shapes) { double area = 0; for(Object obj : shapes) { if (obj instanceof Rectangle) { Rectangle rectangle = (Rectangle)obj; area += rectangle.getWidth() * rectangle.getHeight(); } else { Cricle cricle = (Cricle) obj; area += cricle.getRadius() * cricle.getRadius() + Math.PI; } } return area; } }
Truyền vào một mảng các hình Rectangle và Cricle, kiếm tra nó là gì rồi apply công thức tương ứng với hình là xong. Nhưng nếu bạn cần mở rộng cho hình tam giác, lục giác …. Thì bạn phải làm sao ạ? =)) Tất nhiên là phải đập câu lệnh if else bên trên và thay thế bằng switch case hoặc if else if ….. Lúc này code của mình trông như một đống shit vậy á. Thêm nữa, nếu như mảng chứa 1 triệu record thì mỗi lần nó điều phải kiểm tra như vậy sẽ làm giảm peroformance đáng kể.
Đến dây mình mới thấm cái câu “Một software entities(class, modules, functions, etc..) không nên chỉnh sửa thay vào đó hãy mở rộng chúng”, khi mình viết 1 class, method mà mỗi lần cần sữa đổi mình đều phải đi sửa những class func mà mình đã viết là thôi xong rồi, thằng mình viết ra nó đã xem vào quá nhiều công việc(hóng chuyện) dẫn đến việc nó phải thay đổi quá nhiều khi mà có thay đổi. Một ngày đẹp trời tự nhiên mình thêm chức năng mới, và mình bay vào class method đã có sẵn để modify cho chức năng mới của mình, sau đó các chức năng trước đây nó chết tưoi =)) đó là vì trong quá trình bạn modify class, method cũ đã vô tình thay đổi logic của những chức năng trước.
Thấy không, nếu mình không apply Open closed principle thì code project mình vừa xấu vừa khó đọc lại khí bảo trì, mở rộng nữa. Mở rộng thì sợ hư máy cái cũ, bảo trì thì code như giun bò rồi sữa cái này cái kia hư,,. cuối cùng dự án sẽ fail thôi.
Quay trở lại, rõ ràng là sửa class, method .. đã có sẵn từ trước nó sẽ rất nguy hiểm so với việc bạn thêm một cái gì đó mới thì chắc chắn nó vừa đảm bảo cover chức năng mới, mà lại không ảnh hưởng đến cái cũ.
Để apply Open closed principle, mình có những cách cụ thể như sau
- Sử dụng cơ chế thừa kế
- Sử dụng Composition technique
- Áp dụng Dependency Injection patten
- Áp dụng Decorator pattern
- Áp dụng Strategy pattern
Trong blog này mình sẽ hướng dẫn các bạn cơ chế thừa kế để giải quyết problem này nha
Mình sẽ có class Abstract là Shape
public abstract class Shape { public abstract double area(); }
Rectangle, Cricle extends Shape và implement method area()
public class Cricle extends Shape{ private int radius; @Override public double area() { return radius * radius + Math.PI; } }
public class Rectangle extends Shape{ private int width; private int height; @Override public double area() { return width * height; } }
Bây giờ hàm tính tổng diện tích các hình chỉ đơn giản là loop qua các hình và cộng dồn lại từ kết quả trả về của func area()
public class AreaCalculator { public double Area(Shape[] shapes) { double area = 0; for(Shape shape : shapes) { area += shape.area(); } return area; } }
Sau này nếu muốn mở rộng thêm hình tam giác chẳng hạn chúng ra chỉ cần khai báo class tamgiac extends Shape và implement func area() cho tamgiac vậy là xong.