Tại sao việc sử dụng 'trận chung kết' trên một lớp học thực sự rất tệ?


34

Tôi đang cấu trúc lại một trang web kế thừa PHP OOP.

Tôi rất muốn bắt đầu sử dụng 'trận chung kết' trên các lớp để " make it explicit that the class is currently not extended by anything". Điều này có thể tiết kiệm rất nhiều thời gian nếu tôi đến một lớp học và tôi tự hỏi liệu tôi có thể đổi tên / xóa / sửa đổi một thuộc protectedtính hoặc phương thức hay không. Nếu tôi thực sự muốn mở rộng một lớp, tôi có thể xóa từ khóa cuối cùng để mở khóa .

Tức là nếu tôi đến một lớp học không có lớp con, tôi có thể ghi lại kiến ​​thức đó bằng cách đánh dấu lớp cuối cùng. Lần sau khi tôi đến nó, tôi sẽ không phải tìm kiếm lại cơ sở mã để xem nó có con không. Do đó tiết kiệm thời gian trong quá trình tái cấu trúc.

Tất cả có vẻ như là một ý tưởng tiết kiệm thời gian hợp lý .... nhưng tôi thường đọc rằng các lớp chỉ nên được thực hiện 'cuối cùng' vào những dịp hiếm hoi / đặc biệt.

Có thể nó làm hỏng việc tạo đối tượng Mock hoặc có các tác dụng phụ khác mà tôi không nghĩ tới.

Tôi đang thiếu gì?


3
Nếu bạn có toàn quyền kiểm soát mã của mình, thì nó không khủng khiếp, tôi cho rằng, nhưng một chút về phía OCD. Điều gì nếu bạn bỏ lỡ một lớp học (ví dụ như nó không có con nhưng nó không phải là cuối cùng)? Tốt hơn là để một công cụ quét mã và cho bạn biết liệu một số lớp có trẻ em. Ngay khi bạn đóng gói nó như một thư viện và đưa nó cho người khác, việc xử lý 'trận chung kết' trở thành một nỗi đau.
Công việc

Ví dụ: MbUnit là một khung mở để thử nghiệm đơn vị .Net và MsTest không phải là (bất ngờ bất ngờ). Kết quả là, bạn phải bỏ ra 5k cho MSFT chỉ vì bạn muốn chạy một số thử nghiệm theo cách MSFT. Những người chạy thử nghiệm khác không thể chạy dll thử nghiệm được xây dựng với MsTest - tất cả chỉ vì các lớp cuối cùng mà MSFT đã sử dụng.
Công việc

3
Một số bài đăng trên blog về chủ đề: Lớp cuối cùng: Mở rộng, Đóng để kế thừa (tháng 5 năm 2014; bởi Mathias Verraes)
hakre

Bài viết hay. Nó nói lên suy nghĩ của tôi về điều này. Tôi không biết về những chú thích đó nên rất tiện. Chúc mừng.
JW01

"PHP 5 giới thiệu từ khóa cuối cùng, ngăn các lớp con ghi đè một phương thức bằng cách thêm tiền tố vào định nghĩa cuối cùng. Nếu chính lớp đó được định nghĩa cuối cùng thì nó không thể được mở rộng." php.net/manual/en/lingu.oop5.final.php Nó không tệ chút nào.
Đen

Câu trả lời:


54

Tôi thường đọc rằng các lớp chỉ nên được thực hiện 'cuối cùng' vào những dịp hiếm hoi / đặc biệt.

Bất cứ ai viết đó là sai. Sử dụng finaltự do, không có gì sai với điều đó. Nó ghi nhận rằng một lớp không được thiết kế có tính kế thừa và điều này thường đúng với tất cả các lớp theo mặc định: thiết kế một lớp có thể được kế thừa một cách có ý nghĩa từ nhiều hơn là chỉ xóa một bộ finalxác định; nó cần rất nhiều sự quan tâm

Vì vậy, sử dụng finaltheo mặc định là không có nghĩa là xấu. Trên thực tế, nhiều người đề xuất rằng đây phải là mặc định, ví dụ Jon Skeet .

Có lẽ nó làm hỏng việc tạo đối tượng Mock

Đây thực sự là một cảnh báo, nhưng bạn luôn có thể truy vấn các giao diện nếu bạn cần chế giễu các lớp học của mình. Điều này chắc chắn là vượt trội so với việc làm cho tất cả các lớp mở cho thừa kế chỉ với mục đích chế giễu.


1
1+: Vì nó bắt vít chế nhạo; xem xét việc tạo giao diện hoặc các lớp trừu tượng cho mỗi lớp "cuối cùng".
Spoike

Vâng, điều đó đặt một cờ lê trong công trình. Sự đến muộn này mâu thuẫn với tất cả các câu trả lời khác. Bây giờ tôi không biết phải tin cái gì: o. (Bỏ chọn câu trả lời được chấp nhận của tôi và giữ quyết định trong khi dùng thử lại)
JW01

1
Bạn có thể xem xét chính API Java và tần suất bạn sẽ tìm thấy việc sử dụng từ khóa 'cuối cùng'. Trong thực tế, nó không được sử dụng rất thường xuyên. Nó được sử dụng trong các trường hợp hiệu năng có thể bị hỏng do liên kết động diễn ra khi các phương thức "ảo" được gọi (trong Java tất cả các phương thức là ảo nếu chúng không được khai báo là 'cuối cùng' hoặc 'riêng tư') hoặc khi giới thiệu tùy chỉnh các lớp con của một lớp API Java có thể phát sinh các vấn đề về tính nhất quán, chẳng hạn như phân lớp 'java.lang.String'. Chuỗi Java là một đối tượng giá trị theo định nghĩa và không được thay đổi giá trị của nó sau này. Một lớp con có thể phá vỡ quy tắc này.
Jonny Dee

5
@Jonny Hầu hết các API Java được thiết kế tồi. Hãy nhớ rằng phần lớn của nó đã hơn mười năm tuổi và nhiều thực tiễn tốt nhất đã được phát triển trong thời gian này. Nhưng đừng hiểu ý tôi: chính các kỹ sư Java đã nói như vậy, trước hết và trên hết Josh Bloch, người đã viết chi tiết về điều này, ví dụ như trong Java hiệu quả . Hãy yên tâm rằng nếu API Java được phát triển ngày hôm nay, nó sẽ trông rất khác biệt và finalsẽ đóng vai trò lớn hơn nhiều.
Konrad Rudolph

1
Tôi vẫn không tin rằng việc sử dụng 'trận chung kết' thường xuyên hơn đã trở thành một cách thực hành tốt nhất. Theo tôi, 'trận chung kết' thực sự chỉ nên được sử dụng nếu các yêu cầu về hiệu suất chỉ đạo nó hoặc nếu bạn phải đảm bảo tính nhất quán không thể bị phá vỡ bởi các lớp con. Trong tất cả các trường hợp khác, tài liệu cần thiết phải đi vào, tốt, tài liệu (dù sao cũng cần phải viết), chứ không phải vào mã nguồn như các từ khóa thực thi các mẫu sử dụng nhất định. Đối với tôi, đó là loại thần chơi. Sử dụng chú thích nên là một giải pháp tốt hơn. Người ta có thể thêm chú thích '@final' và trình biên dịch có thể đưa ra cảnh báo.
Jonny Dee

8

Nếu bạn muốn để lại một lưu ý cho bản thân rằng một lớp không có lớp con, thì bằng mọi cách hãy làm như vậy và sử dụng một nhận xét, đó là những gì họ làm. Từ khóa "cuối cùng" không phải là một nhận xét và sử dụng các từ khóa ngôn ngữ chỉ để báo hiệu điều gì đó cho bạn (và chỉ bạn mới biết ý nghĩa của nó) là một ý tưởng tồi.


17
Điều này là hoàn toàn sai! Sử dụng các từ khóa ngôn ngữ chỉ để báo hiệu điều gì đó cho bạn [V]] là một ý tưởng tồi. Khác - Ngược lại, đó chính xác là những từ khóa ngôn ngữ nào. Lời cảnh báo dự kiến ​​của bạn, và chỉ bạn sẽ biết ý nghĩa của nó là gì, tất nhiên là đúng nhưng không áp dụng ở đây; OP đang sử dụng finalnhư mong đợi. Không có gì sai với điều đó. Và sử dụng một tính năng ngôn ngữ để thực thi một ràng buộc luôn vượt trội hơn so với sử dụng một nhận xét.
Konrad Rudolph

8
@Konrad: Ngoại trừ finalnên biểu thị "Không nên tạo lớp con của lớp này" (vì lý do pháp lý hoặc điều gì đó), không phải "lớp này hiện không có con, vì vậy tôi vẫn an toàn để gây rối với các thành viên được bảo vệ". Mục đích của finalnó là phản đề rất "có thể chỉnh sửa tự do" và một finallớp thậm chí không nên có bất kỳ protectedthành viên nào !
SF.

3
@Konrad Rudolph Nó không phải là rất nhỏ. Nếu những người khác sau đó muốn mở rộng các lớp học của mình, có một sự khác biệt lớn giữa một bình luận nói "không được gia hạn" và một từ khóa có nội dung "có thể không mở rộng".
Jeremy

2
@Konrad Rudolph Điều đó nghe có vẻ là một ý kiến ​​tuyệt vời để đưa ra một câu hỏi về thiết kế ngôn ngữ OOP. Nhưng chúng ta biết PHP hoạt động như thế nào và nó sử dụng cách tiếp cận khác là cho phép mở rộng trừ khi được niêm phong. Giả vờ rằng nó ổn để kết hợp các từ khóa bởi vì "đó là như vậy" chỉ là khả năng phòng thủ.
Jeremy

3
@Jeremy Tôi không nhầm lẫn từ khóa. Từ khóa finalcó nghĩa là, lớp này không được mở rộng [bây giờ]. Không có gì hơn, không có gì hơn. Cho dù PHP được thiết kế với triết lý này trong tâm trí là không thích hợp: nó các finaltừ khóa, sau khi tất cả. Thứ hai, tranh luận từ thiết kế của PHP chắc chắn sẽ thất bại, do cách thức chắp vá và chỉ nói chung là PHP được thiết kế tồi.
Konrad Rudolph

5

Có một bài viết hay về "Khi nào khai báo lớp cuối cùng" . Một vài trích dẫn từ nó:

TL; DR: Làm cho các lớp của bạn luôn luôn final, nếu chúng thực hiện một giao diện và không có phương thức công khai nào khác được định nghĩa

Tại sao tôi phải sử dụng final?

  1. Ngăn chặn chuỗi thừa kế lớn của sự diệt vong
  2. Thành phần đáng khích lệ
  3. Buộc nhà phát triển suy nghĩ về API công khai của người dùng
  4. Buộc nhà phát triển thu nhỏ API công khai của đối tượng
  5. Một finallớp luôn có thể được mở rộng
  6. extends phá vỡ đóng gói
  7. Bạn không cần sự linh hoạt đó
  8. Bạn được tự do thay đổi mã

Khi nào nên tránh final:

Các lớp cuối cùng chỉ hoạt động hiệu quả theo các giả định sau:

  1. Có một sự trừu tượng (giao diện) mà lớp cuối cùng thực hiện
  2. Tất cả các API công khai của lớp cuối cùng là một phần của giao diện đó

Nếu một trong hai điều kiện trước này bị thiếu, thì bạn có thể sẽ đạt đến một thời điểm khi bạn sẽ làm cho lớp có thể mở rộng, vì mã của bạn không thực sự dựa vào trừu tượng.

PS Cảm ơn @ocramius đã đọc tuyệt vời!


5

"Cuối cùng" cho một lớp có nghĩa là: Bạn muốn một lớp con? Hãy tiếp tục, xóa "cuối cùng", lớp con bao nhiêu tùy thích, nhưng đừng phàn nàn với tôi nếu nó không hoạt động. Bạn đang ở một mình.

Khi một lớp có thể được phân lớp, đó là hành vi mà người khác dựa vào, phải được mô tả bằng thuật ngữ trừu tượng mà các lớp con tuân theo. Người gọi phải được viết để mong đợi một số thay đổi. Tài liệu phải được viết cẩn thận; bạn không thể nói với mọi người "nhìn vào mã nguồn" bởi vì mã nguồn chưa có. Đó là tất cả nỗ lực. Nếu tôi không mong đợi rằng một lớp được phân lớp, đó là nỗ lực không cần thiết. "Cuối cùng" nói rõ rằng nỗ lực này đã không được thực hiện và đưa ra cảnh báo công bằng.


-1

Một điều bạn có thể không nghĩ đến là thực tế là BẤT K change sự thay đổi của một lớp có nghĩa là nó phải trải qua thử nghiệm QA mới.

Đừng đánh dấu mọi thứ là cuối cùng trừ khi bạn thực sự, thực sự có ý đó.


Cảm ơn. Bạn có thể giải thích thêm. Bạn có nghĩa là nếu tôi đánh dấu một lớp là final(một thay đổi) thì tôi chỉ phải kiểm tra lại nó?
JW01

4
Tôi sẽ nói điều này mạnh mẽ hơn. Không đánh dấu mọi thứ là cuối cùng trừ khi một phần mở rộng không thể được thực hiện để hoạt động vì thiết kế của lớp.
S.Lott

Cảm ơn S.Lott - Tôi đang cố gắng hiểu việc sử dụng cuối cùng ảnh hưởng xấu đến mã / dự án như thế nào. Bạn đã trải qua bất kỳ câu chuyện kinh dị tồi tệ nào dẫn đến việc bạn quá giáo điều về việc sử dụng tiết kiệm final. Đây có phải là kinh nghiệm đầu tay?
JW01

1
@ JW01: Một finallớp có một trường hợp sử dụng chính. Bạn có các lớp đa hình mà bạn không muốn mở rộng vì một lớp con có thể phá vỡ tính đa hình. Đừng sử dụng finaltrừ khi bạn phải ngăn chặn việc tạo các lớp con. Ngoài ra, nó vô dụng.
S.Lott

@ JW01, trong cài đặt sản xuất, bạn có thể KHÔNG ĐƯỢC PHÉP để xóa một bản cuối cùng, bởi vì đây không phải là mã mà khách hàng đã kiểm tra và phê duyệt. Tuy nhiên, bạn có thể được phép thêm mã bổ sung (để thử nghiệm) nhưng bạn không thể làm những gì bạn muốn vì bạn không thể mở rộng lớp học của mình.

-1

Sử dụng 'cuối cùng' sẽ lấy đi sự tự do của những người khác muốn sử dụng mã của bạn.

Nếu mã bạn viết chỉ dành cho bạn và sẽ không bao giờ được phát hành ra công chúng hoặc khách hàng thì bạn có thể làm với mã của mình những gì bạn muốn, tất nhiên. Mặt khác, bạn ngăn người khác xây dựng mã của bạn. Tôi thường xuyên phải làm việc với một API có thể dễ dàng mở rộng cho nhu cầu của mình, nhưng sau đó tôi bị cản trở bởi 'trận chung kết'.

Ngoài ra, thường có mã mà nên tốt hơn không được thực hiện private, nhưng protected. Chắc chắn, privatecó nghĩa là "đóng gói" và ẩn những thứ được coi là chi tiết thực hiện. Nhưng với tư cách là một lập trình viên API, tôi cũng có thể ghi lại thực tế rằng phương pháp đó xyzđược coi là chi tiết triển khai và do đó, có thể bị thay đổi / xóa trong phiên bản tương lai. Vì vậy, tất cả những người sẽ dựa vào mã như vậy bất chấp cảnh báo đang thực hiện nó với rủi ro của riêng mình. Nhưng anh ta thực sự có thể làm điều đó và sử dụng lại mã (hy vọng đã được thử nghiệm) và đưa ra giải pháp nhanh hơn.

Tất nhiên, nếu việc triển khai API là nguồn mở, người ta có thể xóa 'bản cuối cùng' hoặc tạo các phương thức 'được bảo vệ', nhưng hơn là bạn đã thay đổi mã và cần theo dõi các thay đổi của mình dưới dạng các bản vá.

Tuy nhiên, nếu việc triển khai là nguồn đóng, bạn sẽ bị bỏ lại phía sau khi tìm cách khắc phục hoặc trong trường hợp xấu nhất là chuyển sang API khác với ít hạn chế hơn về khả năng tùy chỉnh / mở rộng.

Lưu ý rằng tôi không thấy 'cuối cùng' hoặc 'riêng tư' là xấu, nhưng tôi nghĩ rằng chúng chỉ được sử dụng quá thường xuyên vì lập trình viên không nghĩ về mã của anh ấy về mặt tái sử dụng và mở rộng mã.


2
"Kế thừa không phải là tái sử dụng mã".
quant_dev

2
Ok, nếu bạn nhấn mạnh vào một định nghĩa hoàn hảo, bạn đã đúng. Kế thừa thể hiện mối quan hệ 'is-a' và thực tế này cho phép đa hình hóa và cung cấp một cách mở rộng API. Nhưng trong thực tế, tính kế thừa rất thường được sử dụng để sử dụng lại mã. Điều này đặc biệt là trường hợp có nhiều thừa kế. Và ngay khi bạn gọi mã của một siêu hạng, bạn thực sự đang sử dụng lại mã.
Jonny Dee

@quant_dev: Khả năng tái sử dụng trong OOP có nghĩa là trước hết là đa hình. Và kế thừa là một điểm quan trọng cho hành vi đa hình (chia sẻ giao diện).
Falcon

@Falcon Chia sẻ giao diện! = Chia sẻ mã. Ít nhất là không theo nghĩa thông thường.
quant_dev

3
@quant_dev: Bạn có thể sử dụng lại theo nghĩa OOP sai. Nó có nghĩa là một phương thức duy nhất có thể hoạt động trên nhiều kiểu dữ liệu, nhờ chia sẻ giao diện. Đó là một phần chính của tái sử dụng mã OOP. Và kế thừa tự động cho phép chúng tôi chia sẻ giao diện, do đó tăng cường khả năng sử dụng lại mã rất nhiều. Đó là lý do tại sao chúng ta nói về khả năng sử dụng lại trong OOP. Chỉ cần tạo các thư viện với các thói quen gần như là lập trình và có thể được thực hiện với hầu hết mọi ngôn ngữ, điều đó là phổ biến. Tái sử dụng mã trong OOP còn hơn thế nữa.
Falcon
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.