Về cơ bản chúng tôi muốn mọi thứ hành xử hợp lý.
Hãy xem xét vấn đề sau:
Tôi được cho một nhóm các hình chữ nhật và tôi muốn tăng 10% diện tích của chúng. Vì vậy, những gì tôi làm là tôi đặt chiều dài của hình chữ nhật là 1,1 lần so với trước đây.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
rectangle.Length = rectangle.Length * 1.1;
}
}
Bây giờ trong trường hợp này, tất cả các hình chữ nhật của tôi bây giờ có chiều dài tăng 10%, sẽ tăng diện tích của chúng thêm 10%. Thật không may, ai đó đã thực sự truyền cho tôi một hỗn hợp hình vuông và hình chữ nhật, và khi chiều dài của hình chữ nhật được thay đổi, chiều rộng cũng vậy.
Bài kiểm tra đơn vị của tôi vượt qua vì tôi đã viết tất cả các bài kiểm tra đơn vị của mình để sử dụng bộ sưu tập các hình chữ nhật. Bây giờ tôi đã giới thiệu một lỗi tinh vi vào ứng dụng của mình mà có thể không được chú ý trong nhiều tháng.
Tệ hơn nữa, Jim từ kế toán nhìn thấy phương pháp của tôi và viết một số mã khác sử dụng thực tế là nếu anh ta chuyển các ô vuông vào phương thức của tôi, anh ta sẽ tăng kích thước 21% rất đẹp. Jim hạnh phúc và không ai là khôn ngoan hơn.
Jim được thăng chức cho công việc xuất sắc đến một bộ phận khác. Alfred gia nhập công ty như một đàn em. Trong báo cáo lỗi đầu tiên của mình, Jill từ Advertising đã báo cáo rằng việc truyền bình phương cho phương pháp này dẫn đến tăng 21% và muốn sửa lỗi. Alfred thấy rằng Squares và Hình chữ nhật được sử dụng ở mọi nơi trong mã và nhận ra rằng việc phá vỡ chuỗi thừa kế là không thể. Anh ta cũng không có quyền truy cập vào mã nguồn của Kế toán. Vì vậy, Alfred sửa lỗi như thế này:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle)
{
rectangle.Length = rectangle.Length * 1.1;
}
if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Alfred hài lòng với kỹ năng hack uber của mình và Jill cho biết rằng lỗi đã được sửa.
Tháng tới không ai được trả tiền vì Kế toán phụ thuộc vào việc có thể chuyển bình phương cho IncreaseRectangleSizeByTenPercent
phương thức và được tăng 21% diện tích. Toàn bộ công ty chuyển sang chế độ "ưu tiên 1 lỗi" để theo dõi nguồn gốc của vấn đề. Họ theo dõi vấn đề để sửa chữa của Alfred. Họ biết rằng họ phải giữ cho cả Kế toán và Quảng cáo hạnh phúc. Vì vậy, họ khắc phục sự cố bằng cách xác định người dùng bằng lệnh gọi như vậy:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
IncreaseRectangleSizeByTenPercent(
rectangles,
new User() { Department = Department.Accounting });
}
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
{
rectangle.Length = rectangle.Length * 1.1;
}
else if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Vân vân và vân vân.
Giai thoại này dựa trên các tình huống thực tế mà các lập trình viên phải đối mặt hàng ngày. Vi phạm nguyên tắc thay thế Liskov có thể đưa ra các lỗi rất tinh vi chỉ được phát hiện sau nhiều năm kể từ khi chúng được viết, khi sửa lỗi vi phạm sẽ phá vỡ một loạt điều và không sửa nó sẽ khiến khách hàng lớn nhất của bạn tức giận.
Có hai cách thực tế để khắc phục vấn đề này.
Cách đầu tiên là làm cho hình chữ nhật bất biến. Nếu người dùng Hình chữ nhật không thể thay đổi thuộc tính Chiều dài và Chiều rộng, vấn đề này sẽ biến mất. Nếu bạn muốn một hình chữ nhật có chiều dài và chiều rộng khác nhau, bạn tạo một hình mới. Hình vuông có thể thừa hưởng từ hình chữ nhật một cách hạnh phúc.
Cách thứ hai là phá vỡ chuỗi thừa kế giữa hình vuông và hình chữ nhật. Nếu một hình vuông được định nghĩa là có một thuộc tính duy nhất SideLength
và hình chữ nhật có một Length
và thuộc Width
tính và không có sự thừa kế, thì không thể vô tình phá vỡ mọi thứ bằng cách mong đợi một hình chữ nhật và có được một hình vuông. Trong thuật ngữ C #, bạn có thể seal
lớp hình chữ nhật của mình, điều này đảm bảo rằng tất cả các Hình chữ nhật bạn từng có thực sự là Hình chữ nhật.
Trong trường hợp này, tôi thích cách "khắc phục sự cố" đối tượng. Nhận dạng của một hình chữ nhật là chiều dài và chiều rộng của nó. Nó có ý nghĩa rằng khi bạn muốn thay đổi danh tính của một đối tượng, những gì bạn thực sự muốn là một mới đối tượng. Nếu bạn mất một khách hàng cũ và có được một khách hàng mới, bạn không thay đổi Customer.Id
lĩnh vực từ khách hàng cũ sang khách hàng mới, bạn tạo một khách hàng mới Customer
.
Vi phạm nguyên tắc thay thế Liskov là phổ biến trong thế giới thực, chủ yếu là do rất nhiều mã được viết bởi những người không đủ năng lực / chịu áp lực thời gian / không quan tâm / mắc lỗi. Nó có thể và không dẫn đến một số vấn đề rất khó chịu. Trong hầu hết các trường hợp, bạn muốn ưu tiên thành phần hơn kế thừa .