Ở bài trước chúng ta đã nói về Single Responsibility chữ S đầu tiền trong nguyên tắc SOLID. Hôm nay chúng ta sẽ cùng nhau thảo luận về chữ cái thứ 2, chữ O Open/Closed Principle
Có thể thoải mái mở rộng 1 class nhưng không được sửa đổi bên trong class đó
Hiểu đơn giản là ta có 2 điều cần quan tâm ở đây:
Hạn chế sửa đổi: Ta không nên sửa đổi source code của một class hoặc module vì nó ảnh hưởng đến tính đúng đắn của chương trình
Ưu tiên mở rộng: Để thêm tính năng mới ta nên kế thừa class cha. Class con có những đặc tính của class cha cùng với đó bổ sung tính năng mới để mở rộng
Hình dung 1 cách dễ hiểu thì mình có 1 ví dụ thế này. Chẳng hạn ta đang có 1 chiếc máy ảnh chuyên dụng để chụp chân dung bây giờ ta muốn chụp ảnh khoả thân thì làm thế nào. Chẳng phải chỉ cần cởi quần áo của mẫu ra là chụp được sao.
😀 Mình đùa đấy. Ta có 1 chiếc máy ảnh chụp chân dung. Giờ muốn chụp phong cảnh ta chỉ cần thay 1 chiếc ống kính khác là xong.
Ống kính ở đây cũng giống như những module, class con được mở rộng để phục vụ những mục đích khác nhau giúp ta không cần thay cả 1 chiếc máy ảnh mới.
Vậy ta áp dụng vào phần mềm như thế nào?
Ta cùng nhau xem xét ví dụ sau
Ta có một Rectangle class.
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
}
Giờ ta cần một chương trình tính tổng diện tích các hình chữ nhật.
Theo cách thông thường ta sẽ làm như sau.
public class AreaCalculator
{
public double Area(Rectangle[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Width*shape.Height;
}
return area;
}
}
Sau đó ta lại nhận được yêu cầu mở rộng là chương trình này phải tính được tổng diện tích của hình tròn.
public double Area(object[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle)
{
Rectangle rectangle = (Rectangle) shape;
area += rectangle.Width*rectangle.Height;
}
else
{
Circle circle = (Circle)shape;
area += circle.Radius * circle.Radius * Math.PI;
}
}
return area;
}
Thật tuyệt. Ta vừa phải sửa code và nó đã đáp ứng được yêu cầu.
Nhưng phải làm sao nếu những ngày tới lại có yêu cầu tính diện tích của hình tam giác, hình bình hành ….
Chẳng lẽ lại phải vào sửa và đọc lại đoạn code cũ. Thật là phức tạp và phiền phức.
Để chương trình này có khả năng mở rộng tốt hơn, ta sẽ áp dụng Open/Closed vào đây.
Ta có 1 abstract Shape.
public abstract class Shape
{
public abstract double Area();
}
Rectangle và Circle sẽ kế thừa Shape.
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width*Height;
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Radius*Radius*Math.PI;
}
}
Và sau đó ta sẽ có một chương trình tính tổng diện tích có tính mở rộng cao
public double Area(Shape[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Area();
}
return area;
}
Vậy câu hỏi đặt ra là khi nào ta nên áp dụng nguyên tắc này?
Nguyên tắc này rất quan trọng. Nó xuất hiện ở mọi ngóc ngách của ngành, từ plugin, framework…
Tất nhiên để áp dụng được nguyên tắc này cũng không phải đơn giản. Ta phải xác định được những thứ cần thay đổi.
Việc này đòi hỏi một lập trình viên có nhiều kinh nghiệm và khả năng dự đoán. Nhưng không sao, sai thì sửa thôi. Cứ refactor dần dần rồi mọi thứ sẽ ổn.
Ở bài sau chúng ta sẽ tiếp tục tìm hiểu nguyên tắc thứ 3 Liskov Substitution Principle hay còn gọi là tính khả dĩ thay thế.
Các bạn cùng đón đọc nhé.









(3 lượt thả tim)



