it-swarm-vi.com

Mùi mã: Lạm dụng thừa kế

Nói chung, nó được chấp nhận trong cộng đồng OO rằng người ta nên "ưu tiên thành phần hơn thừa kế". Mặt khác, thừa kế cung cấp cả tính đa hình và cách thức đơn giản, ngắn gọn để ủy thác mọi thứ cho một lớp cơ sở trừ khi được ghi đè rõ ràng và do đó cực kỳ thuận tiện và hữu ích. Đoàn có thể thường xuyên (mặc dù không phải lúc nào) dài dòng và dễ vỡ.

Dấu hiệu rõ ràng nhất và IMHO chắc chắn nhất về lạm dụng thừa kế là vi phạm Nguyên tắc thay thế Liskov . Một số dấu hiệu khác cho thấy sự kế thừa là Công cụ sai cho công việc ngay cả khi nó có vẻ thuận tiện?

52
dsimcha

Khi kế thừa chỉ để có được chức năng, có lẽ bạn đang lạm dụng quyền thừa kế. Điều này dẫn đến việc tạo ra một God Object .

Kế thừa bản thân nó không phải là vấn đề miễn là bạn thấy mối quan hệ thực sự giữa các lớp (như các ví dụ cổ điển, chẳng hạn như Dog mở rộng Động vật) và bạn không đưa các phương thức vào lớp cha mẹ không có ý nghĩa đối với một số lớp trẻ em (ví dụ, thêm phương thức Bark () trong lớp Động vật sẽ không có ý nghĩa gì trong lớp Cá kéo dài Động vật).

Nếu một lớp cần chức năng, hãy sử dụng thành phần (có thể đưa nhà cung cấp chức năng vào hàm tạo?). Nếu một lớp cần TO BE như khác, thì sử dụng kế thừa.

48
Fernando

Tôi có thể nói, chấp nhận rủi ro bị bắn hạ, rằng Kế thừa là một mùi mã trong chính nó :)

Vấn đề với sự kế thừa là nó có thể được sử dụng cho hai mục đích trực giao:

  • giao diện (cho đa hình)
  • thực hiện (để sử dụng lại mã)

Có một cơ chế duy nhất để có được cả hai là nguyên nhân dẫn đến "lạm dụng thừa kế" ngay từ đầu, vì hầu hết mọi người đều mong đợi sự kế thừa là về giao diện, nhưng nó có thể được sử dụng để thực hiện mặc định ngay cả khi các lập trình viên cẩn thận (rất dễ để bỏ qua điều này ...)

Trên thực tế, các ngôn ngữ hiện đại như Haskell hoặc Go, đã từ bỏ quyền thừa kế để tách cả hai mối quan tâm.

Quay lại theo dõi:

Do đó, việc vi phạm Nguyên tắc Liskov là dấu hiệu chắc chắn nhất, vì điều đó có nghĩa là phần "giao diện" của hợp đồng không được tôn trọng.

Tuy nhiên, ngay cả khi giao diện được tôn trọng, bạn có thể có các đối tượng kế thừa từ các lớp cơ sở "béo" chỉ vì một trong các phương thức được coi là hữu ích.

Do đó, Nguyên tắc Liskov tự nó là không đủ, điều bạn cần biết là liệu đa hình có được sử dụng. Nếu không, thì ngay từ đầu đã không có nhiều điểm thừa kế, đó là một sự lạm dụng.

Tóm lược:

  • thi hành Liskov Principle
  • kiểm tra xem nó thực sự được sử dụng

Một cách xung quanh nó:

Áp đặt một sự tách biệt rõ ràng của mối quan tâm:

  • chỉ kế thừa từ các giao diện
  • sử dụng thành phần để đại biểu thực hiện

có nghĩa là ít nhất một vài tổ hợp phím là cần thiết cho mỗi phương thức, và đột nhiên mọi người bắt đầu nghĩ về việc liệu có nên sử dụng lại lớp "béo" này chỉ cho một phần nhỏ của nó hay không.

39
Matthieu M.

Có thực sự "thường được chấp nhận"? Đó là một ý tưởng tôi đã nghe một vài lần, nhưng nó hầu như không phải là một nguyên tắc được công nhận trên toàn cầu. Như bạn đã chỉ ra, tính kế thừa và tính đa hình là đặc điểm nổi bật của OOP. Không có chúng, bạn chỉ có lập trình thủ tục với cú pháp ngớ ngẩn.

Thành phần là một công cụ để xây dựng các đối tượng một cách nhất định. Kế thừa là một công cụ để xây dựng các đối tượng theo một cách khác. Không có gì sai với một trong số họ; bạn chỉ cần biết cả hai hoạt động như thế nào và khi nào thì phù hợp để sử dụng từng phong cách.

14
Mason Wheeler

Sức mạnh của sự kế thừa là tái sử dụng. Của giao diện, dữ liệu, hành vi, hợp tác. Đó là một mẫu hữu ích để truyền tải các thuộc tính cho các lớp mới.

Hạn chế của việc sử dụng thừa kế là tái sử dụng. Giao diện, dữ liệu và hành vi được gói gọn và truyền đi, làm cho nó trở thành một đề xuất tất cả hoặc không có gì. Các vấn đề trở nên đặc biệt rõ ràng khi hệ thống phân cấp trở nên sâu hơn, vì các thuộc tính có thể hữu ích trong bản tóm tắt trở nên ít hơn và bắt đầu che khuất các thuộc tính ở cấp độ địa phương.

Mỗi lớp sử dụng đối tượng sáng tác sẽ phải thực hiện lại ít nhiều cùng một mã để tương tác với nó. Mặc dù việc sao chép này yêu cầu một số vi phạm DRY, nhưng nó cho phép các nhà thiết kế tự do hơn trong việc chọn và chọn các thuộc tính đó của một lớp có ý nghĩa nhất đối với miền của họ. Điều này đặc biệt thuận lợi khi các lớp học trở nên chuyên biệt hơn.

Nói chung, nên tạo các lớp thông qua thành phần, sau đó chuyển đổi sang kế thừa các lớp đó với phần lớn các thuộc tính của chúng, để lại sự khác biệt như các thành phần.

IOW, YAGNI (Bạn không cần phải thừa kế).

5
Huperniketes

Nếu bạn đang phân lớp A đến lớp B nhưng không ai quan tâm nếu B thừa hưởng từ A hay không, thì đó là dấu hiệu cho thấy bạn có thể đang làm sai .

3
tia

"Thành phần ủng hộ thừa kế" là sự khẳng định chắc chắn nhất. Cả hai đều là yếu tố thiết yếu của OOP. Nó giống như nói "Ủng hộ búa trên cưa".

Tất nhiên thừa kế có thể bị lạm dụng, bất kỳ tính năng ngôn ngữ nào cũng có thể bị lạm dụng, các tuyên bố có điều kiện có thể bị lạm dụng.

2
Chuck Stephanski

Có lẽ rõ ràng nhất là khi lớp kế thừa kết thúc với một đống phương thức/thuộc tính không bao giờ được sử dụng trong giao diện tiếp xúc. Trong trường hợp xấu nhất, khi lớp cơ sở có các thành viên trừu tượng, bạn sẽ thấy các triển khai trống (hoặc vô nghĩa) của các thành viên trừu tượng chỉ để 'điền vào chỗ trống' như hiện tại.

Tinh tế hơn, đôi khi bạn thấy nơi nào trong nỗ lực tăng mã tái sử dụng hai điều có triển khai tương tự đã được siết chặt thành một triển khai - mặc dù hai điều đó không liên quan. Điều này có xu hướng tăng sự ghép mã và gỡ rối chúng khi hóa ra sự giống nhau chỉ là sự trùng hợp ngẫu nhiên có thể khá đau đớn trừ khi nó được phát hiện sớm.

Được cập nhật với một vài ví dụ: Mặc dù không liên quan trực tiếp đến lập trình như vậy, tôi nghĩ ví dụ tốt nhất cho loại điều này là với nội địa hóa/quốc tế hóa. Trong tiếng Anh có một từ cho "có". Trong các ngôn ngữ khác có thể là nhiều, nhiều hơn nữa để đồng ý về mặt ngữ pháp với câu hỏi. Ai đó có thể nói, ah chúng ta hãy có một chuỗi cho "có" và điều đó tốt cho nhiều ngôn ngữ. Sau đó, họ nhận ra rằng chuỗi "có" không thực sự tương ứng với "có" mà thực ra là khái niệm "câu trả lời khẳng định cho câu hỏi này" - thực tế là nó "có" mọi lúc chỉ là sự trùng hợp và thời gian chỉ được lưu có một "có" là hoàn toàn mất đi trong việc giải quyết các mớ hỗn độn.

Tôi đã thấy một ví dụ đặc biệt khó chịu về điều này trong cơ sở mã của chúng tôi, nơi thực sự là mối quan hệ cha mẹ -> con cái trong đó cha mẹ và con cái có các thuộc tính có tên tương tự được mô hình hóa bằng thừa kế. Không ai nhận thấy điều này vì mọi thứ dường như đang hoạt động, sau đó hành vi của đứa trẻ (thực sự là cơ sở) và cha mẹ (lớp con) cần phải đi theo các hướng khác nhau. Như may mắn có được, điều này đã xảy ra vào đúng thời điểm khi thời hạn sắp hết - trong nỗ lực Tweak mô hình ở đây và sự phức tạp (cả về logic và sao chép mã) đã đi qua mái nhà ngay lập tức. Thật sự rất khó để lý luận về/làm việc với vì mã đơn giản là không liên quan đến cách doanh nghiệp nghĩ hoặc nói về các khái niệm mà các lớp này thể hiện. Cuối cùng, chúng tôi phải cắn viên đạn và thực hiện một số phép tái cấu trúc lớn có thể tránh được hoàn toàn nếu anh chàng ban đầu đã quyết định rằng một vài thuộc tính âm tương tự với các giá trị xảy ra giống như vậy đại diện cho cùng một khái niệm.

0
FinnNk

Tôi nghĩ rằng những trận chiến như kế thừa so với sáng tác, thực sự chỉ là những khía cạnh của một trận chiến cốt lõi sâu sắc hơn.

Cuộc chiến đó là: điều gì sẽ dẫn đến số lượng mã ít nhất, cho khả năng mở rộng lớn nhất trong các cải tiến trong tương lai?

Đó là lý do tại sao câu hỏi rất khó trả lời. Trong một ngôn ngữ, có thể là sự kế thừa được đưa vào vị trí nhanh hơn so với thành phần so với một số ngôn ngữ khác hoặc ngược lại.

Hoặc nó có thể là vấn đề cụ thể mà bạn đang giải quyết trong mã có lợi nhiều hơn từ kinh nghiệm hơn là tầm nhìn dài hạn về tăng trưởng. Đó là một vấn đề kinh doanh nhiều như một chương trình.

Vì vậy, để quay trở lại câu hỏi, tính kế thừa có thể sai nếu nó đưa bạn vào một chiếc áo khoác thẳng vào một thời điểm nào đó trong tương lai - hoặc nếu nó tạo ra mã không cần phải ở đó (các lớp kế thừa từ người khác mà không có lý do, hoặc tệ hơn nữa là các lớp cơ sở bắt đầu phải làm rất nhiều bài kiểm tra có điều kiện cho trẻ em).

0

Có những tình huống khi kế thừa nên được ưu tiên hơn so với thành phần, và sự khác biệt rõ ràng hơn nhiều so với vấn đề phong cách. Tôi đang diễn giải từ Sutter và Alexandrescu's Tiêu chuẩn mã hóa C++ ở đây vì bản sao của tôi đang ở trên giá sách của tôi tại nơi làm việc vào lúc này. Nhân tiện, rất nên đọc.

Trước hết, sự kế thừa không được khuyến khích khi không cần thiết bởi vì nó tạo ra một mối quan hệ rất mật thiết, gắn bó chặt chẽ giữa các lớp cơ sở và các lớp dẫn xuất có thể gây ra đau đầu cho việc bảo trì. Không chỉ ý kiến ​​của một số người rằng cú pháp thừa kế làm cho nó khó đọc hơn hoặc một cái gì đó.

Điều đó đang được nói, tính kế thừa được khuyến khích khi bạn muốn lớp dẫn xuất được sử dụng lại trong bối cảnh nơi lớp cơ sở hiện đang được sử dụng, mà không phải sửa đổi mã trong ngữ cảnh đó. Ví dụ: nếu bạn có mã bắt đầu giống như if (type1) type1.foo(); else if (type2) type2.foo();, bạn có thể có một trường hợp rất tốt để xem xét thừa kế.

Tóm lại, nếu bạn chỉ muốn sử dụng lại các phương thức lớp cơ sở, hãy sử dụng thành phần. Nếu bạn muốn sử dụng một lớp dẫn xuất trong mã hiện đang sử dụng một lớp cơ sở, hãy sử dụng tính kế thừa.

0
Karl Bielefeldt