Tại sao Square kế thừa từ Hình chữ nhật sẽ có vấn đề nếu chúng ta ghi đè các phương thức SetWidth và SetHeight?


105

Nếu Hình vuông là một loại Hình chữ nhật, thì tại sao Hình vuông không thể kế thừa từ Hình chữ nhật? Hoặc tại sao nó là một thiết kế xấu?

Tôi đã nghe người ta nói:

Nếu bạn đã tạo Quảng trường xuất phát từ Hình chữ nhật, thì Quảng trường sẽ có thể sử dụng được ở bất cứ nơi nào bạn mong đợi hình chữ nhật

vấn đề ở đây là gì? Và tại sao Square có thể sử dụng bất cứ nơi nào bạn mong đợi một hình chữ nhật? Nó chỉ có thể sử dụng được nếu chúng ta tạo đối tượng Square và nếu chúng ta ghi đè các phương thức SetWidth và SetHeight cho Square thì tại sao lại có vấn đề gì?

Nếu bạn đã sử dụng các phương thức SetWidth và SetHeight trên lớp cơ sở Hình chữ nhật của mình và nếu tham chiếu Hình chữ nhật của bạn chỉ vào Hình vuông, thì SetWidth và SetHeight sẽ không có ý nghĩa vì đặt một cái sẽ thay đổi cái khác để phù hợp với nó. Trong trường hợp này, Square thất bại trong bài kiểm tra thay thế Liskov với hình chữ nhật và sự trừu tượng của việc thừa hưởng Square từ hình chữ nhật là một điều tồi tệ.

Ai đó có thể giải thích các lập luận trên? Một lần nữa, nếu chúng ta sử dụng quá nhiều phương thức SetWidth và SetHeight trong Square, liệu nó có giải quyết được vấn đề này không?

Tôi cũng đã nghe / đọc:

Vấn đề thực sự là chúng ta không mô hình hóa các hình chữ nhật, mà là "hình chữ nhật có thể thay đổi được" tức là hình chữ nhật có chiều rộng hoặc chiều cao có thể được sửa đổi sau khi tạo (và chúng ta vẫn coi nó là cùng một đối tượng). Nếu chúng ta nhìn vào lớp hình chữ nhật theo cách này, rõ ràng hình vuông không phải là "hình chữ nhật có thể định hình lại", bởi vì hình vuông không thể được định hình lại và vẫn là hình vuông (nói chung). Về mặt toán học, chúng ta không thấy vấn đề bởi vì tính đột biến thậm chí không có ý nghĩa trong bối cảnh toán học

Ở đây tôi tin rằng "tái kích cỡ" là thuật ngữ chính xác. Hình chữ nhật là "kích thước lại" và hình vuông cũng vậy. Tôi có thiếu điều gì trong lập luận trên không? Một hình vuông có thể được kích thước lại như bất kỳ hình chữ nhật.


15
Câu hỏi này có vẻ hết sức trừu tượng. Có rất nhiều cách để sử dụng các lớp và kế thừa, việc có tạo ra một số lớp kế thừa từ một số lớp hay không là một ý tưởng tốt thường phụ thuộc chủ yếu vào cách bạn muốn sử dụng các lớp đó. Nếu không có trường hợp thực tế tôi không thể thấy câu hỏi này có thể nhận được câu trả lời phù hợp như thế nào.
aaaaaaaaaaaa

2
Sử dụng một số ý nghĩa thông thường, người ta nhớ rằng hình vuông một hình chữ nhật, vì vậy nếu đối tượng của lớp hình vuông của bạn không thể được sử dụng trong trường hợp hình chữ nhật được yêu cầu thì có lẽ đó là một lỗ hổng thiết kế ứng dụng.
Cthulhu

7
Tôi nghĩ câu hỏi tốt hơn là Why do we even need Square? Nó giống như có hai cây bút. Một cây bút màu xanh và một cây bút màu xanh đỏ, vàng hoặc xanh lá cây. Bút màu xanh là dư thừa - thậm chí nhiều hơn trong trường hợp hình vuông vì nó không có lợi ích chi phí.
Gusdor

2
@eBusiness Tính trừu tượng của nó là điều làm cho nó trở thành một câu hỏi học tập tốt. Điều quan trọng là có thể nhận ra việc sử dụng phân nhóm nào là xấu độc lập với các trường hợp sử dụng cụ thể.
Doval

5
@Cthulhu Không hẳn. Subtyping là tất cả về hành vi và một hình vuông có thể thay đổi không hoạt động giống như một hình chữ nhật có thể thay đổi. Đây là lý do tại sao ẩn dụ "là một ..." là xấu.
Doval

Câu trả lời:


189

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 IncreaseRectangleSizeByTenPercentphươ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 SideLengthvà hình chữ nhật có một Lengthvà thuộc Widthtí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ể seallớ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.Idlĩ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 .


7
Liskov là một chuyện, và lưu trữ là một vấn đề khác. Trong hầu hết các triển khai, một phiên bản Square kế thừa từ Hình chữ nhật sẽ cần không gian để lưu trữ hai chiều, mặc dù chỉ cần một chiều.
el.pescado

29
Sử dụng tuyệt vời một câu chuyện để minh họa điểm
Rory Hunter

29
Câu chuyện hay nhưng tôi không đồng ý. Trường hợp sử dụng là: thay đổi diện tích của hình chữ nhật. Việc khắc phục nên được thêm một phương thức có thể ghi đè 'ChangeArea' vào hình chữ nhật được chuyên biệt trong Square. Điều này sẽ không phá vỡ chuỗi thừa kế, làm rõ những gì người dùng muốn làm và sẽ không gây ra lỗi được giới thiệu bởi bản sửa lỗi được đề cập của bạn (sẽ bị bắt trong khu vực tổ chức phù hợp).
Roy T.

33
@RoyT.: Tại sao hình chữ nhật nên biết cách đặt khu vực của nó? Đó là một tài sản có nguồn gốc hoàn toàn từ chiều dài và chiều rộng. Và hơn thế nữa, nó nên thay đổi kích thước nào - chiều dài, chiều rộng hoặc cả hai?
cHao

32
@Roy T. Thật tuyệt khi nói rằng bạn đã giải quyết vấn đề theo cách khác, nhưng thực tế đây là một ví dụ - mặc dù đơn giản hóa - về các tình huống trong thế giới thực mà các nhà phát triển phải đối mặt hàng ngày khi duy trì các sản phẩm cũ. Và ngay cả khi bạn đã thực hiện phương pháp đó, điều đó sẽ không ngăn những người thừa kế vi phạm LSP và đưa ra các lỗi tương tự như phương pháp này. Đây là lý do tại sao khá nhiều lớp trong .NET framework bị niêm phong.
Stephen

30

Nếu tất cả các đối tượng của bạn là bất biến, không có vấn đề. Mỗi hình vuông cũng là một hình chữ nhật. Tất cả các thuộc tính của Hình chữ nhật cũng là thuộc tính của Hình vuông.

Vấn đề bắt đầu khi bạn thêm khả năng sửa đổi các đối tượng. Hoặc thực sự - khi bạn bắt đầu truyền đối số cho đối tượng, không chỉ đọc các biểu đồ thuộc tính.

Có những sửa đổi mà bạn có thể thực hiện đối với Hình chữ nhật duy trì tất cả các bất biến của lớp Hình chữ nhật của bạn, nhưng không phải tất cả các bất biến Square - như thay đổi chiều rộng hoặc chiều cao. Đột nhiên, hành vi của Hình chữ nhật không chỉ là thuộc tính của nó, nó cũng là sửa đổi có thể có của nó. Đó không chỉ là những gì bạn nhận được từ Hình chữ nhật, đó cũng là những gì bạn có thể đặt vào .

Nếu Hình chữ nhật của bạn có một phương pháp setWidthđược ghi nhận là thay đổi chiều rộng và không sửa đổi chiều cao, thì Square không thể có phương thức tương thích. Nếu bạn thay đổi chiều rộng và không phải chiều cao, kết quả không còn là Hình vuông hợp lệ. Nếu bạn chọn sửa đổi cả chiều rộng và chiều cao của Hình vuông khi sử dụng setWidth, bạn không thực hiện thông số kỹ thuật của Hình chữ nhật setWidth. Bạn không thể thắng.

Khi bạn nhìn vào những gì bạn có thể "đặt vào" Hình chữ nhật và Hình vuông, bạn có thể gửi những tin nhắn nào cho họ, bạn có thể thấy rằng bất kỳ tin nhắn nào bạn có thể gửi hợp lệ tới Quảng trường, bạn cũng có thể gửi đến Hình chữ nhật.

Đó là vấn đề của phương sai so với phương sai.

Các phương thức của một lớp con thích hợp, một trong đó các trường hợp có thể được sử dụng trong tất cả các trường hợp mà siêu lớp dự kiến, yêu cầu mỗi phương thức phải:

  • Chỉ trả về các giá trị mà siêu lớp sẽ trả về - nghĩa là kiểu trả về phải là một kiểu con của kiểu trả về của phương thức siêu lớp. Trả lại là đồng biến thể.
  • Chấp nhận tất cả các giá trị mà siêu kiểu sẽ chấp nhận - nghĩa là các kiểu đối số phải là siêu kiểu của các kiểu đối số của phương thức siêu lớp. Đối số là biến thể chống lại.

Vì vậy, quay lại Hình chữ nhật và Hình vuông: Việc Square có thể là một lớp con của Hình chữ nhật hay không hoàn toàn phụ thuộc vào phương thức Hình chữ nhật có.

Nếu Hình chữ nhật có các setters riêng cho chiều rộng và chiều cao, Square sẽ không tạo một lớp con tốt.

Tương tự, nếu bạn thực hiện một số phương thức là đồng biến trong các đối số, như có compareTo(Rectangle)trên Hình chữ nhật và compareTo(Square)trên Hình vuông, bạn sẽ gặp vấn đề khi sử dụng Hình vuông làm Hình chữ nhật.

Nếu bạn thiết kế Hình vuông và Hình chữ nhật của mình tương thích, nó có thể sẽ hoạt động, nhưng chúng sẽ được phát triển cùng nhau, hoặc tôi cá là nó sẽ không hoạt động.


"Nếu tất cả các đối tượng của bạn là bất biến, không có vấn đề gì" - đây rõ ràng là tuyên bố không liên quan trong bối cảnh của câu hỏi này, trong đó đề cập rõ ràng đến setters cho chiều rộng và chiều cao
gnat

11
Tôi thấy điều này thú vị, ngay cả khi nó "dường như không liên quan"
Jesvin Jose

14
@gnat Tôi cho rằng nó có liên quan vì giá trị thực của câu hỏi được nhận ra khi có mối quan hệ phân nhóm hợp lệ giữa hai loại. Điều đó phụ thuộc vào hoạt động mà siêu kiểu khai báo, do đó, đáng để chỉ ra vấn đề sẽ biến mất nếu các phương thức biến đổi biến mất.
Doval

1
@gnat cũng vậy, setters là người gây đột biến , vì vậy lrn về cơ bản là nói, "Đừng làm vậy và đó không phải là vấn đề." Tôi tình cờ đồng ý với tính bất biến đối với các loại đơn giản, nhưng bạn đưa ra một điểm tốt: Đối với các đối tượng phức tạp, vấn đề không đơn giản như vậy.
Patrick M

1
Hãy xem xét nó theo cách này, hành vi được đảm bảo bởi lớp 'Hình chữ nhật' là gì? Rằng bạn có thể thay đổi chiều rộng và chiều cao ĐỘC LẬP lẫn nhau. (tức là phương thức setWidth và setHeight). Bây giờ nếu Square có nguồn gốc từ Hình chữ nhật, Square phải đảm bảo hành vi này. Vì hình vuông không thể đảm bảo hành vi này, đó là một thừa kế xấu. Tuy nhiên, nếu các phương thức setWidth / setHeight bị xóa khỏi lớp Hình chữ nhật, thì không có hành vi nào như vậy và do đó bạn có thể lấy được lớp Square từ Hình chữ nhật.
Nitin Bhide

17

Có rất nhiều câu trả lời hay ở đây; Câu trả lời của Stephen đặc biệt làm tốt công việc minh họa tại sao vi phạm nguyên tắc thay thế dẫn đến xung đột trong thế giới thực giữa các đội.

Tôi nghĩ rằng tôi có thể nói ngắn gọn về vấn đề cụ thể của hình chữ nhật và hình vuông, thay vì sử dụng nó như một phép ẩn dụ cho các vi phạm khác của LSP.

Có một vấn đề khác với hình vuông là một hình chữ nhật đặc biệt hiếm khi được đề cập, và đó là: tại sao chúng ta dừng lại với hình vuông và hình chữ nhật ? Nếu chúng ta sẵn sàng nói rằng hình vuông là một loại hình chữ nhật đặc biệt thì chắc chắn chúng ta cũng nên sẵn sàng nói:

  • Hình vuông là một loại hình thoi đặc biệt - đó là hình thoi có góc vuông.
  • Hình thoi là một loại hình bình hành đặc biệt - đó là hình bình hành có các cạnh bằng nhau.
  • Hình chữ nhật là một loại hình bình hành đặc biệt - đó là hình bình hành có các góc vuông
  • Một hình chữ nhật, hình vuông và hình bình hành đều là một loại hình thang đặc biệt - chúng là hình thang có hai bộ cạnh song song
  • Tất cả các bên trên là các loại hình tứ giác đặc biệt
  • Tất cả các bên trên là các loại hình phẳng đặc biệt
  • Và như vậy; Tôi có thể tiếp tục đi một thời gian ở đây.

Điều gì trên trái đất nên tất cả các mối quan hệ ở đây? Các ngôn ngữ dựa trên kế thừa lớp như C # hoặc Java không được thiết kế để thể hiện các loại mối quan hệ phức tạp này với nhiều loại ràng buộc khác nhau. Tốt nhất là tránh câu hỏi hoàn toàn bằng cách không cố gắng thể hiện tất cả những điều này dưới dạng các lớp có mối quan hệ phụ.


3
Nếu các đối tượng hình dạng là bất biến, thì người ta có thể có một IShapeloại bao gồm một hộp giới hạn và có thể được vẽ, thu nhỏ và nối tiếp và có một IPolygonkiểu con với một phương thức để báo cáo số lượng đỉnh và phương thức trả về một IEnumerable<Point>. Sau đó, người ta có thể có IQuadrilateralkiểu con xuất phát từ IPolygon, IRhombusIRectangle, xuất phát từ đó, và ISquarexuất phát từ IRhombusIRectangle. Tính tương tác sẽ ném mọi thứ ra ngoài cửa sổ và nhiều kế thừa không hoạt động với các lớp, nhưng tôi nghĩ nó ổn với các giao diện bất biến.
supercat

Tôi thực sự không đồng ý với Eric ở đây (mặc dù không đủ cho -1!). Tất cả những mối quan hệ này (có thể) có liên quan, như @supercat đề cập; đó chỉ là vấn đề của YAGNI: bạn không thực hiện nó cho đến khi bạn cần nó.
Đánh dấu Hurd

Câu trả lời rất hay! Nên cách cao hơn.
andrew.fox

1
@MarkHurd - đó không phải là vấn đề của YAGNI: hệ thống phân cấp dựa trên thừa kế được đề xuất có hình dạng phân loại được mô tả, nhưng không thể viết để đảm bảo các mối quan hệ xác định nó. Làm thế nào để IRhombusđảm bảo rằng tất cả Pointtrả về từ Enumerable<Point>được xác định bởi IPolygontương ứng với các cạnh có độ dài bằng nhau? Bởi vì việc thực hiện IRhombusgiao diện một mình không đảm bảo rằng một đối tượng cụ thể là hình thoi, nên sự kế thừa có thể là câu trả lời.
A. Rager

14

Từ góc độ toán học, một hình vuông là - một hình chữ nhật. Nếu một nhà toán học sửa đổi hình vuông để nó không còn tuân thủ hợp đồng hình vuông, nó sẽ thay đổi thành hình chữ nhật.

Nhưng trong thiết kế OO, đây là một vấn đề. Một đối tượng là những gì nó là, và điều này bao gồm các hành vi cũng như trạng thái. Nếu tôi giữ một đối tượng hình vuông, nhưng người khác sửa đổi nó thành hình chữ nhật, điều đó vi phạm hợp đồng của hình vuông mà không có lỗi của riêng tôi. Điều này gây ra tất cả các loại điều xấu xảy ra.

Yếu tố quan trọng ở đây là tính đột biến . Một hình dạng có thể thay đổi một khi nó được xây dựng?

  • Có thể thay đổi: nếu hình dạng được phép thay đổi một khi được xây dựng, hình vuông không thể có mối quan hệ is-a với hình chữ nhật. Hợp đồng của một hình chữ nhật bao gồm các ràng buộc mà các mặt đối diện phải có độ dài bằng nhau, nhưng các mặt liền kề không cần phải có. Hình vuông phải có bốn cạnh bằng nhau. Sửa đổi hình vuông thông qua giao diện hình chữ nhật có thể vi phạm hợp đồng hình vuông.

  • Bất biến: nếu hình dạng không thể thay đổi một khi được xây dựng, thì một đối tượng hình vuông cũng phải luôn hoàn thành hợp đồng hình chữ nhật. Một hình vuông có thể có một mối quan hệ is-với hình chữ nhật.

Trong cả hai trường hợp, có thể yêu cầu một hình vuông tạo ra một hình dạng mới dựa trên trạng thái của nó với một hoặc nhiều thay đổi. Ví dụ, người ta có thể nói "tạo một hình chữ nhật mới dựa trên hình vuông này, ngoại trừ hai cạnh đối diện A và C dài gấp đôi". Vì một đối tượng mới đang được xây dựng, hình vuông ban đầu tiếp tục tuân thủ các hợp đồng của nó.


1
This is one of those cases where the real world is not able to be modeled in a computer 100%. Tại sao vậy? Chúng ta vẫn có thể có một mô hình chức năng của hình vuông và hình chữ nhật. Hậu quả duy nhất là chúng ta phải tìm kiếm một cấu trúc đơn giản hơn để trừu tượng hơn hai đối tượng đó.
Simon Bergot

6
Có nhiều điểm chung giữa hình chữ nhật và hình vuông hơn thế. Vấn đề là danh tính của hình chữ nhật và danh tính của hình vuông là độ dài cạnh của nó (và góc ở mỗi giao điểm). Giải pháp tốt nhất ở đây là làm cho hình vuông được thừa hưởng từ hình chữ nhật, nhưng làm cho cả hai không thay đổi.
Stephen

3
@Stephen Đồng ý. Trên thực tế, làm cho chúng bất biến là điều hợp lý để làm bất kể vấn đề phụ. Không có lý do gì để biến chúng thành đột biến - không khó để xây dựng một hình vuông hoặc hình chữ nhật mới hơn là biến đổi một hình vuông, vậy tại sao lại mở những con giun đó? Bây giờ bạn không phải lo lắng về răng cưa / tác dụng phụ và bạn có thể sử dụng chúng làm chìa khóa cho bản đồ / dicts nếu cần. Một số người sẽ nói "hiệu suất", theo đó tôi sẽ nói "tối ưu hóa sớm" cho đến khi họ thực sự đo được và chứng minh rằng điểm nóng nằm trong mã hình dạng.
Doval

Xin lỗi các bạn, đã muộn và tôi đã rất mệt mỏi khi viết câu trả lời. Tôi viết lại nó để nói những gì tôi thực sự có ý nghĩa, mấu chốt của nó là tính đột biến.

13

Và tại sao Square có thể sử dụng bất cứ nơi nào bạn mong đợi một hình chữ nhật?

Bởi vì đó là một phần của ý nghĩa của một kiểu con (xem thêm: Nguyên tắc thay thế Liskov). Bạn có thể làm, cần phải có khả năng làm điều này:

Square s = new Square(5);
Rect r = s;
doSomethingWith(r); // written assuming a Rect, actually calls Square methods

Bạn thực sự làm điều này mọi lúc (đôi khi thậm chí còn ngầm hơn) khi sử dụng OOP.

và nếu chúng ta quá lạm dụng các phương thức SetWidth và SetHeight cho Square hơn thì tại sao lại có vấn đề gì?

Bởi vì bạn không thể ghi đè một cách hợp lý những người cho Square. Bởi vì hình vuông không thể "có kích thước lại như bất kỳ hình chữ nhật nào". Khi chiều cao của hình chữ nhật thay đổi, chiều rộng vẫn giữ nguyên. Nhưng khi chiều cao của hình vuông thay đổi, chiều rộng phải thay đổi tương ứng. Vấn đề không chỉ là có thể thay đổi kích thước, nó có thể được tái kích hoạt ở cả hai chiều một cách độc lập.


Trong rất nhiều ngôn ngữ, bạn thậm chí không cần Rect r = s;đường dây, bạn chỉ có thể doSomethingWith(s)và bộ thực thi sẽ sử dụng bất kỳ cuộc gọi nào sđể giải quyết với bất kỳ Squarephương thức ảo nào .
Patrick M

1
@PatrickM Bạn không cần nó trong bất kỳ ngôn ngữ lành mạnh nào có phân nhóm. Tôi bao gồm dòng đó để giải thích, để được rõ ràng.

Vì vậy, ghi đè setWidthsetHeightđể thay đổi cả chiều rộng và chiều cao.
Tiếp cậnDarknessFish

@ValekHalfHeart Đó chính xác là lựa chọn tôi đang xem xét.

7
@ValekHalfHeart: Đó chính xác là sự vi phạm Nguyên tắc thay thế Liskov sẽ ám ảnh bạn và khiến bạn mất nhiều đêm mất ngủ để cố gắng tìm ra một lỗi lạ hai năm sau đó khi bạn quên mất cách thức hoạt động của mã.
Jan Hudec

9

Những gì bạn đang mô tả chạy theo những gì được gọi là Nguyên tắc thay thế Liskov . Ý tưởng cơ bản của LSP là bất cứ khi nào bạn sử dụng một thể hiện của một lớp cụ thể, bạn sẽ luôn có thể trao đổi trong một thể hiện của bất kỳ lớp con nào của lớp đó, mà không đưa ra lỗi.

Vấn đề Hình chữ nhật-Quảng trường thực sự không phải là một cách hay để giới thiệu Liskov. Nó cố gắng giải thích một nguyên tắc rộng bằng cách sử dụng một ví dụ thực sự khá tinh tế và chạy afoul của một trong những định nghĩa trực quan phổ biến nhất trong tất cả các toán học. Một số người gọi đó là vấn đề Ellipse-Circle vì lý do đó, nhưng nó chỉ tốt hơn một chút cho đến nay. Một cách tiếp cận tốt hơn là lùi lại một bước, sử dụng vấn đề mà tôi gọi là vấn đề Hình bình hành-Hình chữ nhật. Điều này làm cho mọi thứ dễ hiểu hơn nhiều.

Hình bình hành là một hình tứ giác có hai cặp cạnh song song. Nó cũng có hai cặp góc đồng dạng. Không khó để tưởng tượng một đối tượng Parallelogram dọc theo các dòng này:

class Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

Một cách phổ biến để nghĩ về một hình chữ nhật là hình bình hành với các góc vuông. Thoạt nhìn, điều này dường như có thể làm cho Hình chữ nhật trở thành một ứng cử viên tốt để kế thừa từ Parallelogram , để bạn có thể sử dụng lại tất cả các mã tốt. Tuy nhiên:

class Rectangle extends Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* BUG: Liskov violations ahead */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

Tại sao hai hàm này giới thiệu các lỗi trong Hình chữ nhật? Vấn đề là bạn không thể thay đổi các góc trong hình chữ nhật : chúng được xác định là luôn luôn là 90 độ, và vì vậy giao diện này không thực sự hoạt động đối với Hình chữ nhật kế thừa từ Hình bình hành. Nếu tôi hoán đổi một hình chữ nhật thành mã mong đợi một hình bình hành và mã đó cố gắng thay đổi góc, gần như chắc chắn sẽ có lỗi. Chúng tôi đã lấy thứ gì đó có thể ghi được trong lớp con và làm cho nó chỉ đọc và đó là vi phạm Liskov.

Bây giờ, làm thế nào điều này áp dụng điều này cho Hình vuông và Hình chữ nhật?

Khi chúng tôi nói rằng bạn có thể đặt một giá trị, chúng tôi thường có nghĩa là một cái gì đó mạnh hơn một chút so với việc chỉ có thể viết một giá trị vào nó. Chúng tôi ngụ ý một mức độ độc quyền nhất định: nếu bạn đặt một giá trị, sau đó loại bỏ một số trường hợp đặc biệt, nó sẽ giữ nguyên giá trị đó cho đến khi bạn đặt lại giá trị đó. Có rất nhiều cách sử dụng cho các giá trị có thể được ghi vào nhưng không được đặt, nhưng cũng có nhiều trường hợp phụ thuộc vào giá trị ở nơi mà nó được đặt sau khi bạn đặt nó. Và đó là nơi chúng ta gặp phải một vấn đề khác.

class Square extends Rectangle {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};

    /* BUG: More Liskov violations */
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* Liskov violations inherited from Rectangle */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

Lớp Square của chúng tôi thừa hưởng các lỗi từ Hình chữ nhật, nhưng nó có một số lỗi mới. Vấn đề với setSideA và setSideB là cả hai điều này không thực sự ổn định được nữa: bạn vẫn có thể viết một giá trị vào một trong hai, nhưng nó sẽ thay đổi từ bên dưới nếu cái kia được ghi vào. Nếu tôi trao đổi mã này cho một Parallelogram trong mã phụ thuộc vào việc có thể đặt các mặt độc lập với nhau, thì nó sẽ trở nên kỳ dị.

Đó là vấn đề, và đó là lý do tại sao có vấn đề với việc sử dụng Hình chữ nhật làm giới thiệu về Liskov. Hình chữ nhật-Square phụ thuộc vào sự khác biệt giữa khả năng ghi vào một cái gì đó và có thể đặt nó, và đó là một sự khác biệt tinh tế hơn nhiều so với việc có thể đặt một cái gì đó so với chỉ đọc. Hình chữ nhật-Square vẫn có giá trị như một ví dụ, bởi vì nó ghi lại một hình ảnh xác thực khá phổ biến phải được theo dõi, nhưng nó không nên được sử dụng làm ví dụ giới thiệu . Hãy để người học có được một số nền tảng cơ bản trước, và sau đó ném một cái gì đó khó hơn vào chúng.


8

Subtyping là về hành vi.

Để loại Blà một kiểu con A, nó phải hỗ trợ mọi thao tác mà loại Ahỗ trợ với cùng một ngữ nghĩa (nói chuyện ưa thích cho "hành vi"). Sử dụng lý do mà mỗi B là A không không làm việc - khả năng tương thích hành vi có tiếng nói cuối cùng. Hầu hết thời gian "B là một loại A" trùng lặp với "B cư xử như A", nhưng không phải lúc nào cũng vậy .

Một ví dụ:

Hãy xem xét tập hợp các số thực. Trong bất kỳ ngôn ngữ, chúng ta có thể mong đợi họ để hỗ trợ các hoạt động +, -, *, và /. Bây giờ hãy xem xét tập hợp các số nguyên dương ({1, 2, 3, ...}). Rõ ràng, mọi số nguyên dương cũng là một số thực. Nhưng loại số nguyên dương có phải là kiểu con của loại số thực không? Hãy xem xét bốn phép toán và xem các số nguyên dương có hoạt động giống như các số thực hay không:

  • +: Chúng tôi có thể thêm số nguyên dương mà không có vấn đề.
  • -: Không phải tất cả các phép trừ của số nguyên dương đều dẫn đến số nguyên dương. Ví dụ 3 - 5.
  • *: Chúng ta có thể nhân các số nguyên dương mà không gặp vấn đề gì.
  • /: Chúng tôi không thể luôn chia số nguyên dương và lấy số nguyên dương. Ví dụ 5 / 3.

Vì vậy, mặc dù số nguyên dương là tập con của số thực, chúng không phải là một kiểu con. Một đối số tương tự có thể được thực hiện cho các số nguyên có kích thước hữu hạn. Rõ ràng mỗi số nguyên 32 bit cũng là số nguyên 64 bit, nhưng 32_BIT_MAX + 1sẽ cho bạn kết quả khác nhau cho từng loại. Vì vậy, nếu tôi đưa cho bạn một số chương trình và bạn đã thay đổi loại của mỗi biến số nguyên 32 bit thành số nguyên 64 bit, rất có thể chương trình sẽ hoạt động khác đi (điều này hầu như luôn có nghĩa sai ).

Tất nhiên, bạn có thể xác định +ints 32 bit để kết quả là số nguyên 64 bit, nhưng bây giờ bạn sẽ phải dự trữ 64 bit không gian mỗi khi bạn thêm hai số 32 bit. Điều đó có thể hoặc có thể không được bạn chấp nhận tùy thuộc vào nhu cầu bộ nhớ của bạn.

Vì sao vấn đề này?

Điều quan trọng là các chương trình phải chính xác. Nó được cho là tài sản quan trọng nhất cho một chương trình để có. Nếu một chương trình đúng với một số loại A, cách duy nhất để đảm bảo chương trình đó sẽ tiếp tục đúng với một số kiểu con Blà nếu Bhành xử như Amọi cách.

Vì vậy, bạn có loại Rectangles, có đặc điểm kỹ thuật nói rằng các mặt của nó có thể được thay đổi độc lập. Bạn đã viết một số chương trình sử dụng Rectanglesvà giả định việc thực hiện tuân theo đặc tả. Sau đó, bạn đã giới thiệu một kiểu con được gọi là Squarecác mặt không thể thay đổi kích thước một cách độc lập. Do đó, hầu hết các chương trình thay đổi kích thước hình chữ nhật bây giờ sẽ sai.


6

Nếu một hình vuông là một loại hình chữ nhật hơn tại sao không thể một hình vuông thừa hưởng từ hình chữ nhật? Hoặc tại sao nó là một thiết kế xấu?

Trước tiên, hãy tự hỏi tại sao bạn nghĩ hình vuông là hình chữ nhật.

Tất nhiên hầu hết mọi người đã học được điều đó ở trường tiểu học, và nó có vẻ hiển nhiên. Một hình chữ nhật là một hình dạng 4 mặt với các góc 90 độ, và một hình vuông đáp ứng tất cả các tính chất đó. Vì vậy, không phải là một hình vuông một hình chữ nhật?

Mặc dù vậy, tất cả phụ thuộc vào tiêu chí ban đầu của bạn để phân nhóm các đối tượng, bối cảnh bạn đang xem xét các đối tượng này là gì. Trong hình dạng hình học được phân loại dựa trên các thuộc tính của điểm, đường và thiên thần của chúng.

Vì vậy, trước khi bạn nói "hình vuông là một loại hình chữ nhật" trước tiên bạn phải tự hỏi mình, điều này có dựa trên các tiêu chí tôi quan tâm không .

Trong phần lớn các trường hợp, đó sẽ không phải là điều bạn quan tâm. Phần lớn các hệ thống mô hình hóa hình dạng, chẳng hạn như GUI, đồ họa và trò chơi video, không liên quan chủ yếu đến việc nhóm hình học của một đối tượng mà đó là hành vi. Bạn đã bao giờ làm việc trên một hệ thống quan trọng rằng hình vuông là một loại hình chữ nhật theo nghĩa hình học. Điều gì thậm chí sẽ cung cấp cho bạn, biết rằng nó có 4 cạnh và góc 90 độ?

Bạn không mô hình hóa một hệ thống tĩnh, bạn đang mô hình hóa một hệ thống động nơi mọi thứ sẽ xảy ra (hình dạng sẽ được tạo ra, phá hủy, thay đổi, vẽ, v.v.). Trong bối cảnh này, bạn quan tâm đến hành vi được chia sẻ giữa các đối tượng, bởi vì mối quan tâm chính của bạn là những gì bạn có thể làm với hình dạng, những quy tắc nào phải được duy trì để vẫn có một hệ thống mạch lạc.

Trong bối cảnh này, hình vuông chắc chắn không phải là hình chữ nhật , bởi vì các quy tắc chi phối cách hình vuông có thể được thay đổi không giống với hình chữ nhật. Vì vậy, chúng không phải là cùng một loại.

Trong trường hợp không mô hình chúng như vậy. Tại sao bạn? Nó mang lại cho bạn không có gì ngoài một hạn chế không cần thiết.

Nó chỉ có thể sử dụng được nếu chúng ta tạo đối tượng Square và nếu chúng ta ghi đè các phương thức SetWidth và SetHeight cho Square thì tại sao lại có vấn đề gì?

Nếu bạn làm điều đó mặc dù thực tế bạn đang nói rõ về mã rằng chúng không giống nhau. Mã của bạn sẽ nói một hình vuông hành xử theo cách này và một hình chữ nhật hành xử theo cách đó nhưng chúng vẫn giống nhau.

Chúng rõ ràng không giống nhau trong bối cảnh mà bạn quan tâm vì bạn chỉ xác định hai hành vi khác nhau. Vậy tại sao lại giả vờ giống nhau nếu chúng chỉ giống nhau trong bối cảnh bạn không quan tâm?

Điều này nhấn mạnh một vấn đề quan trọng khi các nhà phát triển đến một miền mà họ muốn mô hình hóa. Điều quan trọng là phải làm rõ bối cảnh bạn quan tâm trước khi bạn bắt đầu nghĩ về các đối tượng trong miền. Bạn quan tâm đến khía cạnh nào. Hàng ngàn năm trước, người Hy Lạp quan tâm đến các đặc tính chung của các đường và thiên thần của hình dạng, và nhóm chúng dựa trên những điều này. Điều đó không có nghĩa là bạn buộc phải tiếp tục nhóm đó nếu đó không phải là điều bạn quan tâm (điều mà trong 99% thời gian mô hình hóa trong phần mềm bạn sẽ không quan tâm).

Rất nhiều câu trả lời cho câu hỏi này tập trung vào việc gõ phụ là về hành vi nhóm 'khiến chúng trở thành quy tắc .

Nhưng điều quan trọng là phải hiểu rằng bạn không làm điều này chỉ để tuân theo các quy tắc. Bạn đang làm điều này bởi vì trong phần lớn các trường hợp, đây là điều bạn thực sự quan tâm. Bạn không quan tâm nếu một hình vuông và hình chữ nhật có chung các thiên thần bên trong. Bạn quan tâm đến những gì họ có thể làm trong khi vẫn là hình vuông và hình chữ nhật. Bạn quan tâm đến hành vi của các đối tượng bởi vì bạn đang mô hình hóa một hệ thống tập trung vào việc thay đổi hệ thống dựa trên các quy tắc hành vi của các đối tượng.


Nếu các biến kiểu Rectanglechỉ được sử dụng để biểu diễn các giá trị , thì có thể một lớp có Squarethể kế thừa từ Rectanglevà tuân thủ đầy đủ hợp đồng của nó. Thật không may, nhiều ngôn ngữ không tạo ra sự khác biệt giữa các biến đóng gói các giá trị và các biến xác định các thực thể.
supercat

Có thể, nhưng sau đó tại sao phải bận tâm ở nơi đầu tiên. Vấn đề của vấn đề hình chữ nhật / hình vuông không phải là cố gắng tìm ra cách làm cho mối quan hệ "hình vuông là hình chữ nhật" hoạt động, mà là nhận ra rằng mối quan hệ không thực sự tồn tại trong bối cảnh bạn đang sử dụng các đối tượng (về mặt hành vi) và như một lời cảnh báo về việc không áp đặt các mối quan hệ không liên quan lên miền của bạn.
Cormac Mulhall

Hoặc nói cách khác: Đừng thử và uốn cong cái muỗng. Không thể nào. Thay vào đó chỉ cố gắng nhận ra sự thật, rằng không có muỗng. :-)
Cormac Mulhall

1
Có một Squareloại bất biến kế thừa từ một Rectnagleloại bất biến có thể hữu ích nếu có một số loại hoạt động chỉ có thể được thực hiện trên các ô vuông. Như một ví dụ thực tế của khái niệm này, hãy xem xét một ReadableMatrixloại [loại cơ sở một mảng hình chữ nhật có thể được lưu trữ theo nhiều cách khác nhau, bao gồm cả thưa thớt] và một ComputeDeterminantphương thức. Có thể có ý nghĩa khi ComputeDeterminantchỉ làm việc với một ReadableSquareMatrixloại có nguồn gốc từ ReadableMatrix, mà tôi sẽ coi là một ví dụ về Squarexuất phát từ a Rectangle.
supercat

5

Nếu một hình vuông là một loại hình chữ nhật hơn tại sao không thể một hình vuông thừa hưởng từ hình chữ nhật?

Vấn đề nằm ở chỗ nghĩ rằng nếu mọi thứ có liên quan theo một cách nào đó trong thực tế, thì chúng phải liên quan theo cùng một cách chính xác sau khi mô hình hóa.

Điều quan trọng nhất trong mô hình hóa là xác định các thuộc tính chung và các hành vi phổ biến, xác định chúng trong lớp cơ bản và thêm các thuộc tính bổ sung trong các lớp con.

Vấn đề với ví dụ của bạn là, điều đó hoàn toàn trừu tượng. Miễn là không ai biết, bạn dự định sử dụng các lớp đó để làm gì, thật khó để đoán bạn nên làm thiết kế nào. Nhưng nếu bạn thực sự muốn chỉ có chiều cao, chiều rộng và thay đổi kích thước, thì sẽ hợp lý hơn:

  • định nghĩa Square là lớp cơ sở, với widththam số và resize(double factor)thay đổi kích thước chiều rộng theo hệ số đã cho
  • định nghĩa lớp Hình chữ nhật và lớp con của Square, bởi vì nó thêm một thuộc tính khác heightvà ghi đè resizehàm của nó , nó gọi super.resizevà sau đó thay đổi kích thước chiều cao theo hệ số đã cho

Từ quan điểm lập trình, không có gì trong Square, hình chữ nhật đó không có. Không có ý nghĩa làm cho Square là lớp con của Hình chữ nhật.


+1 Chỉ vì hình vuông là một loại chỉnh lưu đặc biệt trong toán học, không có nghĩa là nó giống nhau trong OO.
Lovis

1
Một hình vuông là một hình vuông và một hình chữ nhật là một hình chữ nhật. Các mối quan hệ giữa họ cũng nên giữ trong mô hình, hoặc bạn có một mô hình khá kém. Vấn đề thực sự là: 1) nếu bạn biến chúng thành biến đổi, bạn không còn mô hình hình vuông và hình chữ nhật; 2) giả sử rằng chỉ vì một số mối quan hệ "là" giữ giữa hai loại đối tượng, bạn có thể thay thế một loại cho loại kia một cách bừa bãi.
Doval

4

Bởi vì theo LSP, việc tạo mối quan hệ thừa kế giữa hai và ghi đè setWidthsetHeightđể đảm bảo hình vuông có cả hai cùng giới thiệu hành vi khó hiểu và không trực quan. Hãy nói rằng chúng tôi có một mã:

Rectangle r = createRectangle(); // create rectangle or square here
r.setWidth(10);
r.setHeight(20);
print(r.getWidth()); // expect to print 10
print(r.getHeight()); // expect to print 20

Nhưng nếu phương thức createRectangletrả về Square, vì có thể nhờ Squarekế thừa từ Rectange. Sau đó, những kỳ vọng bị phá vỡ. Ở đây, với mã này, chúng tôi hy vọng cài đặt chiều rộng hoặc chiều cao sẽ chỉ gây ra thay đổi về chiều rộng hoặc chiều cao tương ứng. Điểm của OOP là khi bạn làm việc với siêu lớp, bạn không có kiến ​​thức về bất kỳ lớp con nào bên dưới nó. Và nếu lớp con thay đổi hành vi để nó đi ngược lại với những kỳ vọng mà chúng ta có về siêu lớp, thì có khả năng cao sẽ xảy ra lỗi. Và những loại lỗi này đều khó gỡ lỗi và sửa chữa.

Một trong những ý tưởng chính về OOP là nó là hành vi, không phải dữ liệu được kế thừa (cũng là một trong những quan niệm sai lầm chính của IMO). Và nếu bạn nhìn vào hình vuông và hình chữ nhật, chúng không có hành vi nào mà chúng ta có thể liên quan đến quan hệ thừa kế.


2

Những gì LSP nói là bất cứ điều gì kế thừa Rectanglephải là a Rectangle. Đó là, nó nên làm bất cứ điều gì a Rectangle.

Có lẽ tài liệu cho Rectangleđược viết để nói rằng hành vi của một người Rectangleđược đặt tên rnhư sau:

r.setWidth(10);
r.setHeight(20);
print(r.getWidth());  // prints 10

Nếu Quảng trường của bạn không có hành vi tương tự thì nó sẽ không hoạt động như một Rectangle. Vì vậy, LSP nói rằng nó không được thừa kế từ Rectangle. Ngôn ngữ không thể thực thi quy tắc này, vì nó không thể ngăn bạn làm điều gì đó sai trong ghi đè phương thức, nhưng điều đó không có nghĩa là "nó ổn vì ngôn ngữ cho phép tôi ghi đè các phương thức" là một lý lẽ thuyết phục để thực hiện nó!

Bây giờ, có thể viết tài liệu Rectangletheo cách mà nó không ngụ ý rằng đoạn mã trên in 10, trong trường hợp đó có thể là của bạn Squarecó thể là a Rectangle. Bạn có thể thấy tài liệu có nội dung như "đây là X. Hơn nữa, việc triển khai trong lớp này thực hiện Y". Nếu vậy thì bạn có một trường hợp tốt để trích xuất một giao diện từ lớp và phân biệt giữa những gì giao diện đảm bảo và những gì lớp đảm bảo ngoài đó. Nhưng khi mọi người nói "hình vuông có thể thay đổi không phải là hình chữ nhật có thể thay đổi, trong khi hình vuông bất biến là hình chữ nhật bất biến", về cơ bản họ cho rằng hình trên thực sự là một phần của định nghĩa hợp lý của hình chữ nhật có thể thay đổi.


điều này dường như chỉ lặp lại các điểm được giải thích trong một câu trả lời được đăng 5 giờ trước
gnat

@gnat: bạn có muốn tôi chỉnh sửa câu trả lời khác đó xuống gần bằng mức độ ngắn gọn này không? ;-) Tôi không nghĩ rằng mình có thể mà không loại bỏ những điểm mà người trả lời khác có lẽ cảm thấy cần thiết để trả lời câu hỏi và tôi cảm thấy không.
Steve Jessop


1

Các kiểu con và, bằng cách mở rộng, lập trình OO, thường dựa vào Nguyên tắc thay thế Liskov, rằng bất kỳ giá trị nào của loại A có thể được sử dụng khi yêu cầu B, nếu A <= B. Đây là một tiên đề trong kiến ​​trúc OO, nghĩa là. giả định rằng tất cả các lớp con sẽ có thuộc tính này (và nếu không, các kiểu con là lỗi và cần phải sửa).

Tuy nhiên, hóa ra nguyên tắc này là không thực tế / không thể hiện được hầu hết các mã, hoặc thực sự không thể thỏa mãn (trong các trường hợp không tầm thường)! Vấn đề này, được gọi là vấn đề hình chữ nhật vuông hoặc vấn đề hình elip hình tròn ( http://en.wikipedia.org/wiki/Circle-ellipse_probols ) là một ví dụ nổi tiếng về mức độ khó thực hiện.

Lưu ý rằng chúng ta có thể thực hiện các Hình vuông và Hình chữ nhật tương đương nhiều hơn và quan sát hơn, nhưng chỉ bằng cách vứt bỏ ngày càng nhiều chức năng cho đến khi sự khác biệt là vô ích.

Ví dụ: xem http://okmij.org/ftp/Computing/Subtyping/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.