Bỏ thừa kế trong ngôn ngữ lập trình


10

Tôi đang phát triển ngôn ngữ lập trình của riêng tôi. Đó là ngôn ngữ có mục đích chung (nghĩ rằng Python đã gõ tĩnh cho máy tính để bàn, nghĩa là int x = 1;) không dành cho đám mây.

Bạn có nghĩ rằng không nên cho phép thừa kế hoặc Mixins? (cho rằng người dùng ít nhất sẽ có giao diện)

Ví dụ: Google Go, Một ngôn ngữ hệ thống gây sốc cho cộng đồng lập trình bằng cách không cho phép kế thừa.

Câu trả lời:


4

Trên thực tế, chúng ta thấy ngày càng nhiều rằng việc sử dụng tính kế thừa là tối ưu phụ bởi vì (trong số những thứ khác) nó dẫn đến khớp nối chặt chẽ.

Kế thừa triển khai luôn có thể được thay thế bằng kế thừa giao diện cộng với thành phần và các thiết kế phần mềm hiện đại hơn có xu hướng đi theo hướng sử dụng kế thừa ngày càng ít hơn, có lợi cho thành phần.

Vì vậy, , không cung cấp thừa kế nói riêng là một quyết định thiết kế hoàn toàn hợp lệ và rất thịnh hành .

Mixins, mặt khác, thậm chí không (chưa) một tính năng ngôn ngữ chủ đạo, và ngôn ngữ mà ta cung cấp một tính năng gọi là “mixin” thường hiểu những điều rất khác nhau bởi nó. Hãy cung cấp nó, hoặc không. Cá nhân tôi thấy nó rất hữu ích nhưng thực hiện nó có thể rất khó.


13

Lý do về việc kế thừa (hoặc bất kỳ tính năng đơn lẻ nào, thực sự) có cần thiết hay không mà không xem xét phần còn lại của ngữ nghĩa của ngôn ngữ là vô nghĩa; bạn đang cãi nhau trong chân không

Những gì bạn cần là một triết lý thiết kế ngôn ngữ nhất quán; ngôn ngữ cần có khả năng giải quyết vấn đề một cách tao nhã. Mô hình để đạt được điều này có thể hoặc không cần kế thừa, nhưng thật khó để đánh giá điều này nếu không có bức tranh lớn.

Ví dụ, nếu ngôn ngữ của bạn có các hàm hạng nhất, ứng dụng hàm một phần, kiểu dữ liệu đa hình, biến kiểu và kiểu chung, bạn có khá nhiều cơ sở giống như bạn có với kế thừa OOP cổ điển, nhưng sử dụng mô hình khác.

Nếu bạn có ràng buộc muộn, gõ động, thuộc tính phương thức, đối số hàm linh hoạt và hàm hạng nhất, bạn cũng bao gồm cùng một căn cứ, nhưng một lần nữa, sử dụng mô hình khác.

(Tìm các ví dụ cho hai mô hình được phác thảo là một bài tập cho người đọc.)

Vì vậy, hãy suy nghĩ về loại ngữ nghĩa mà bạn muốn, chơi xung quanh với chúng và xem liệu chúng có đủ mà không cần thừa kế. Nếu họ không, bạn có thể quyết định ném thừa kế vào hỗn hợp, hoặc bạn có thể quyết định rằng thiếu một cái gì đó khác.


9

Đúng.

Tôi nghĩ rằng không cho phép thừa kế là tốt, đặc biệt nếu ngôn ngữ của bạn được gõ động. Bạn có thể đạt được tái sử dụng mã tương tự bằng cách, ví dụ, ủy quyền hoặc thành phần. Ví dụ, tôi đã viết một số chương trình khá phức tạp - okay, không phức tạp:) - trong JavaScript mà không cần bất kỳ thừa kế. Về cơ bản tôi đã sử dụng các đối tượng như các cấu trúc dữ liệu đại số với một số hàm được đính kèm như các phương thức. Tôi cũng có một loạt các hàm không phải là phương thức hoạt động trên các đối tượng này.

Nếu bạn có kiểu gõ động - và tôi giả sử bạn làm thế - bạn cũng có thể có đa hình mà không cần kế thừa. Ngoài ra, nếu bạn cho phép thêm các phương thức tùy ý vào các đối tượng trong thời gian chạy, bạn sẽ không thực sự cần những thứ như mixins rất nhiều.

Một tùy chọn khác - mà tôi nghĩ là một lựa chọn tốt - là mô phỏng JavaScript và sử dụng các nguyên mẫu. Đây là đơn giản hơn các lớp học nhưng cũng rất linh hoạt. Chỉ cần một cái gì đó để xem xét.

Vì vậy, tất cả đã nói, tôi sẽ đi cho nó.


2
Miễn là có một cách hợp lý để hoàn thành các loại nhiệm vụ thường được xử lý bằng kế thừa, hãy tiếp tục. Bạn có thể thử một vài trường hợp sử dụng, để đảm bảo (thậm chí có thể đi xa hơn để thu hút các vấn đề ví dụ từ người khác, để tránh tự thiên vị ...)
sắp diễn ra vào

Không Nó được gõ một cách tĩnh và mạnh mẽ, tôi sẽ đề cập đến điều đó ở trên cùng.
Christopher

Vì nó được nhập tĩnh, bạn không thể thêm các phương thức ngẫu nhiên vào các đối tượng khi chạy. Tuy nhiên, bạn vẫn có thể gõ vịt: kiểm tra các giao thức của Gosu để biết ví dụ. Tôi đã sử dụng các giao thức một chút trong mùa hè và chúng thực sự hữu ích. Gosu cũng có "cải tiến" là cách thêm phương thức vào các lớp sau thực tế. Bạn có thể xem xét thêm một cái gì đó như thế là tốt.
Tikhon Jelvis

2
Xin vui lòng, xin vui lòng, xin vui lòng, ngừng thừa kế liên quan và tái sử dụng mã. Từ lâu, người ta đã biết rằng thừa kế là công cụ thực sự tồi để tái sử dụng mã.
Jan Hudec

3
Kế thừa chỉ là một công cụ để tái sử dụng mã. Thường thì nó là một công cụ rất tốt , và những lần khác thì thật kinh khủng. Ưu tiên thành phần hơn thừa kế không có nghĩa là không bao giờ sử dụng thừa kế.
Michael K

8

Vâng, đó là một quyết định thiết kế hoàn toàn hợp lý để bỏ qua sự kế thừa.

Trong thực tế, có những lý do rất tốt để loại bỏ kế thừa thực hiện, vì nó có thể tạo ra một số mã cực kỳ phức tạp và khó duy trì. Tôi thậm chí còn đi xa hơn khi coi sự kế thừa (vì nó thường được triển khai trong hầu hết các ngôn ngữ OOP) như là một hành vi sai trái.

Clojure, chẳng hạn, không cung cấp sự kế thừa thực hiện, muốn cung cấp một tập hợp các tính năng trực giao (giao thức, dữ liệu, hàm, macro) có thể được sử dụng để đạt được kết quả tương tự nhưng sạch sẽ hơn nhiều.

Đây là một video mà tôi thấy rất ngộ về chủ đề chung này, trong đó Rich Hickey xác định các nguồn phức tạp cơ bản trong các ngôn ngữ lập trình (bao gồm cả kế thừa) và trình bày các lựa chọn thay thế cho mỗi: Đơn giản được thực hiện dễ dàng


Trong nhiều trường hợp, kế thừa giao diện là phù hợp hơn. Nhưng nếu được sử dụng với mức độ vừa phải, kế thừa hàm ý có thể cho phép loại bỏ rất nhiều mã soạn sẵn, và là giải pháp tao nhã nhất. Nó chỉ yêu cầu các lập trình viên thông minh và cẩn thận hơn khỉ Python / JavaScript trung bình.
Erik Alapää

4

Khi tôi lần đầu tiên chạy qua thực tế là các lớp VB6 không hỗ trợ kế thừa (chỉ giao diện), nó thực sự làm tôi khó chịu (và vẫn vậy).

Tuy nhiên, lý do rất tệ là vì nó cũng không có tham số hàm tạo, vì vậy bạn không thể thực hiện phép tiêm phụ thuộc thông thường (DI). Nếu bạn có DI thì điều đó quan trọng hơn kế thừa vì bạn có thể tuân theo nguyên tắc ưu tiên sáng tác hơn thừa kế. Dù sao đó cũng là cách tốt hơn để sử dụng lại mã.

Không có Mixins mặc dù? Nếu bạn muốn thực hiện một giao diện và ủy thác tất cả công việc của giao diện đó cho một đối tượng được đặt thông qua việc tiêm phụ thuộc, thì Mixins là lý tưởng. Nếu không, bạn phải viết tất cả các mã soạn sẵn để ủy quyền mọi phương thức và / hoặc thuộc tính cho đối tượng con. Tôi làm điều đó rất nhiều (nhờ C #) và đó là một điều tôi ước mình không phải làm.


Vâng, điều khiến cho câu hỏi này là việc triển khai DOMG DOM trong đó việc sử dụng lại mã rất có lợi.
Christopher

2

Để không đồng ý với câu trả lời khác: không, bạn bỏ qua các tính năng khi chúng không tương thích với thứ bạn muốn nhiều hơn. Java (và các ngôn ngữ GC'd khác) đã loại bỏ việc quản lý bộ nhớ rõ ràng vì nó muốn loại an toàn hơn. Haskell đã ném ra đột biến vì nó muốn lý luận tương đương và các loại ưa thích hơn. Thậm chí C đã ném ra (hoặc tuyên bố là bất hợp pháp) một số loại răng cưa và hành vi khác vì nó muốn tối ưu hóa trình biên dịch nhiều hơn.

Vì vậy, câu hỏi là: Bạn muốn gì hơn là thừa kế?


1
Chỉ có quản lý bộ nhớ rõ ràng và an toàn loại là hoàn toàn không liên quan và chắc chắn không tương thích. Áp dụng tương tự cho các thể loại đột biến và ưa thích. Tôi đồng ý với lý luận chung nhưng các ví dụ của bạn được chọn khá tệ.
Konrad Rudolph

@KonradRudolph, quản lý bộ nhớ và loại an toàn có liên quan chặt chẽ với nhau. A free/ deletehoạt động cung cấp cho bạn khả năng vô hiệu hóa các tài liệu tham khảo; trừ khi hệ thống loại của bạn có thể theo dõi tất cả các tài liệu tham khảo bị ảnh hưởng, điều đó làm cho ngôn ngữ không an toàn. Đặc biệt, cả C và C ++ đều không an toàn. Đúng là bạn có thể thỏa hiệp bằng cách này hay cách khác (ví dụ: loại tuyến tính hoặc hạn chế phân bổ) để khiến họ đồng ý. Nói chính xác tôi nên nói rằng Java muốn loại an toàn với một hệ thống loại đơn giản, cụ thể và phân bổ không hạn chế hơn.
Ryan Culpepper

Vâng, điều đó phụ thuộc vào cách bạn xác định loại an toàn. Chẳng hạn, nếu bạn lấy định nghĩa (hoàn toàn hợp lý) về an toàn kiểu thời gian biên dịch muốn tính toàn vẹn tham chiếu là một phần của hệ thống loại của bạn, thì Java cũng không phải là loại an toàn (vì nó cho phép nulltham chiếu). Cấm freelà một hạn chế bổ sung khá tùy tiện. Tóm lại, việc một ngôn ngữ là loại an toàn hay không phụ thuộc nhiều vào định nghĩa của bạn về an toàn loại hơn là ngôn ngữ.
Konrad Rudolph

1
@Ryan: Con trỏ hoàn toàn an toàn. Họ luôn cư xử theo định nghĩa của họ. Nó có thể không phải là cách bạn thích chúng, nhưng nó luôn luôn theo cách chúng được xác định. Bạn đang cố gắng kéo dài chúng thành một cái gì đó không phải là chúng. Con trỏ thông minh có thể đảm bảo an toàn bộ nhớ khá tầm thường trong C ++.
DeadMG

@KonradRudolph: Trong Java, mọi Ttham chiếu đều đề cập đến một nullhoặc một đối tượng của một lớp mở rộng T; nulllà xấu, nhưng các hoạt động nullném một ngoại lệ được xác định rõ hơn là làm hỏng loại bất biến ở trên. Tương phản với C ++: sau khi gọi đến delete, một T*con trỏ có thể trỏ đến bộ nhớ không còn giữ Tđối tượng. Tồi tệ hơn, thực hiện một phép gán trường bằng con trỏ đó trong một phép gán, bạn có thể cập nhật hoàn toàn một trường của một đối tượng của một lớp khác nếu nó chỉ được đặt ở một địa chỉ gần đó. Đó không phải là loại an toàn theo bất kỳ định nghĩa hữu ích của thuật ngữ.
Ryan Culpepper

1

Không.

Nếu bạn muốn loại bỏ một tính năng ngôn ngữ cơ bản, thì bạn đang tuyên bố rằng nó không bao giờ, không bao giờ cần thiết (hoặc không chính đáng để thực hiện, không áp dụng ở đây). Và "không bao giờ" là một từ mạnh mẽ trong công nghệ phần mềm. Bạn sẽ cần một lời biện minh rất mạnh mẽ để đưa ra tuyên bố như vậy.


8
Điều đó hầu như không đúng: Toàn bộ thiết kế của Java là về việc loại bỏ các tính năng như quá tải toán tử, nhiều kế thừa và quản lý bộ nhớ thủ công. Ngoài ra, tôi nghĩ rằng việc coi bất kỳ tính năng ngôn ngữ nào là "thiêng liêng" là sai; thay vì biện minh cho việc xóa một tính năng, bạn nên biện minh cho việc thêm nó - tôi không thể nghĩ ra bất kỳ tính năng nào mà mọi ngôn ngữ phải có.
Tikhon Jelvis

6
@TikhonJelvis: Đó là lý do tại sao Java là một ngôn ngữ tồi tệ và nó không có bất cứ thứ gì ngoại trừ "SỬ DỤNG CÔNG CỤ GARBAGE-THU THẬP" là lý do số một tại sao. Các tính năng ngôn ngữ cần phải được chứng minh, tôi đồng ý, nhưng đây là một tính năng ngôn ngữ cơ bản - nó có rất nhiều ứng dụng hữu ích và không thể được lập trình viên sao chép mà không vi phạm DRY.
DeadMG

1
@DeadMG wow! Ngôn ngữ khá mạnh.
Christopher

@DeadMG: Tôi bắt đầu viết phản bác như một bình luận nhưng sau đó tôi đã biến nó thành một câu trả lời (qv).
Ryan Culpepper

1
"Sự hoàn hảo được đạt được không phải khi không còn gì để thêm mà là không còn gì để xóa" (q)
9000

1

Phát biểu từ quan điểm C ++ nghiêm ngặt, kế thừa rất hữu ích cho hai mục đích chính:

  1. Nó cho phép khả năng phục hồi mã
  2. Kết hợp với ghi đè trong lớp con, nó cho phép bạn sử dụng một con trỏ lớp cơ sở để xử lý đối tượng lớp con mà không cần biết loại của nó.

Đối với điểm 1 miễn là bạn có một số cách để chia sẻ mã mà không phải thưởng thức quá nhiều màn nhào lộn, bạn có thể loại bỏ tính kế thừa. Đối với điểm 2. Bạn có thể đi theo cách java và nhấn mạnh vào một giao diện để thực hiện tính năng này.

Lợi ích của việc loại bỏ thừa kế là

  1. ngăn chặn hệ thống phân cấp dài và các vấn đề liên quan của nó. Cơ chế chia sẻ mã của bạn phải đủ tốt cho việc đó.
  2. Tránh thay đổi trong các lớp cha mẹ từ phá vỡ các lớp con.

Sự đánh đổi chủ yếu là giữa "Đừng lặp lại chính mình" và Linh hoạt ở một bên và tránh vấn đề ở phía bên kia. Cá nhân tôi rất ghét thấy sự kế thừa đi từ C ++ đơn giản vì một số nhà phát triển khác có thể không đủ thông minh để thấy trước các vấn đề.


1

Bạn có nghĩ rằng không nên cho phép thừa kế hoặc Mixins? (cho rằng người dùng ít nhất sẽ có giao diện)

Bạn có thể thực hiện rất nhiều công việc rất hữu ích mà không cần thực hiện kế thừa hoặc mixins, nhưng tôi tự hỏi liệu bạn có nên có một kiểu kế thừa giao diện nào đó không, tức là một tuyên bố cho biết nếu một đối tượng thực hiện giao diện A thì nó cũng cần giao diện B (nghĩa là A là một chuyên ngành của B và do đó có một mối quan hệ loại). Mặt khác, đối tượng kết quả của bạn chỉ cần ghi lại rằng nó thực hiện cả hai giao diện, do đó không có quá nhiều phức tạp ở đó. Tất cả hoàn toàn có thể làm được.

Việc thiếu kế thừa triển khai có một nhược điểm rõ ràng: bạn sẽ không thể xây dựng các vtables được lập chỉ mục số cho các lớp của mình và do đó sẽ phải thực hiện tra cứu băm cho mỗi lệnh gọi phương thức (hoặc tìm ra một cách thông minh để tránh chúng). Điều này có thể gây đau đớn nếu bạn định tuyến ngay cả các giá trị cơ bản (ví dụ: số) thông qua cơ chế này. Ngay cả một triển khai băm rất tốt cũng có thể tốn kém khi bạn nhấn nó nhiều lần trong mỗi vòng lặp bên trong!

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.