Tại sao có các lĩnh vực riêng tư, không được bảo vệ đủ?


265

Khả năng hiển thị privatecủa các trường lớp / thuộc tính / thuộc tính có hữu ích không? Trong OOP, sớm hay muộn, bạn sẽ tạo một lớp con của một lớp và trong trường hợp đó, thật tốt để hiểu và có thể sửa đổi hoàn toàn việc thực hiện.

Một trong những điều đầu tiên tôi làm khi tôi phân lớp một lớp là thay đổi một loạt các privatephương thức thành protected. Tuy nhiên, việc ẩn các chi tiết từ thế giới bên ngoài là rất quan trọng vì vậy chúng tôi cần protectedvà không chỉ public.

Câu hỏi của tôi là: Bạn có biết về một trường hợp sử dụng quan trọng trong privateđó thay vì protectedlà một công cụ tốt, hoặc hai tùy chọn " protected& public" có đủ cho các ngôn ngữ OOP không?



236
Đối với những người bị hạ bệ: Mặc dù tôi cũng không đồng ý với các cơ sở của OP, tôi vẫn ủng hộ câu hỏi này vì nó hoàn toàn mạch lạc và đáng để trả lời. Đúng, OP cần được cho biết lý do tại sao điều này sai, nhưng cách để làm điều đó là viết một câu trả lời (hoặc đề nghị chỉnh sửa các câu trả lời hiện có), chứ không phải hạ thấp chỉ vì anh ta chưa tự mình tìm ra.
Ixrec

18
Các lớp phái sinh là một phần của thế giới bên ngoài.
CodeInChaos

20
Đừng quên rằng được bảo vệ không phải lúc nào cũng có nghĩa là quyền truy cập bị khóa trong hệ thống phân cấp thừa kế. Trong Java, nó cũng cấp quyền truy cập cấp gói.
berry120

8
Giáo sư của tôi thường nói rằng "Có những điều tôi sẽ không nói với các con tôi. Đó là những lĩnh vực riêng tư của tôi."
SáT

Câu trả lời:


225

Bởi vì như bạn nói, protectedvẫn để lại cho bạn khả năng "sửa đổi hoàn thành việc thực hiện". Nó không thực sự bảo vệ bất cứ điều gì trong lớp.

Tại sao chúng ta quan tâm đến việc "bảo vệ thực sự" những thứ bên trong lớp học? Bởi vì nếu không , sẽ không thể thay đổi chi tiết thực hiện mà không phá vỡ mã máy khách . Nói cách khác, những người viết các lớp con cũng là "thế giới bên ngoài" cho người đã viết lớp cơ sở ban đầu.

Trong thực tế, protectedcác thành viên về cơ bản là một "API công khai cho các lớp con" và cần duy trì ổn định và tương thích ngược giống như publiccác thành viên. Nếu chúng tôi không có khả năng tạo privatethành viên thực sự , thì không có gì trong việc triển khai sẽ an toàn để thay đổi, bởi vì bạn sẽ không thể loại trừ khả năng mã khách hàng (không độc hại) bằng cách nào đó phụ thuộc vào nó

Ngẫu nhiên, trong khi "Trong OOP, sớm hay muộn, bạn sẽ tạo một lớp con của một lớp" là đúng về mặt kỹ thuật, lập luận của bạn dường như đưa ra giả định mạnh mẽ hơn rằng "sớm hay muộn, bạn sẽ tạo ra một lớp con mỗi lớp "gần như chắc chắn không phải vậy.


11
Tôi sẽ ném cho bạn nhiều hơn +1 nếu tôi có thể, vì đây là lần đầu tiên privatetôi thực sự có ý nghĩa với tôi. Phối cảnh lib cần được sử dụng thường xuyên hơn, nếu không, bất kỳ ai thích tâm lý mã hóa "giống như C, kiểm soát tuyệt đối", có thể đánh dấu nó là "bảo vệ tôi khỏi chính tôi". Lưu ý rằng tôi vẫn sử dụng privatetrước đây, tôi chỉ luôn cảm thấy tài liệu tốt và một quy ước đặt tên muốn _foocho biết có lẽ bạn không nên nhầm lẫn với nó là tương đương nếu không tốt hơn. Có thể nói một cách privatedứt khoát rằng "không có gì sẽ phá vỡ" là một tính năng hợp pháp, duy nhất.
abluejelly 11/03/2016

Ban đầu tôi đã bỏ qua trường hợp mã của các thư viện và khung công cộng và chỉ nghĩ ít nhiều về "mã khách hàng". Tối ưu hóa việc thực hiện nội bộ là một ví dụ tốt cho câu hỏi của tôi, mặc dù tôi tự hỏi mình, nếu điều này thực sự xảy ra trong thực tế (đặc biệt là khi nhiều người đề xuất một lớp không nên dài hơn 1000 dòng mã). Nói chung, tôi thích cách tiếp cận của Ruby, nơi riêng tư là một loại khuyến nghị: "Ở đây là những con rồng, hãy tiến hành cẩn thận".
Adam Libuša

9
@ AdamLibuša Mặc dù đây là một thỏa thuận lớn hơn nhiều nếu mã của bạn là công khai, nó vẫn được áp dụng ngay cả khi bạn là tác giả của tất cả các khách hàng của lớp. Vấn đề chỉ đơn giản là thay đổi từ một số nhà tái cấu trúc nhất định thành không thể đối với một số nhà tái cấu trúc nhất định là tẻ nhạt và dễ bị lỗi. Tối ưu hóa thực sự là lý do ít phổ biến nhất cho các nhà tái cấu trúc này theo kinh nghiệm của tôi (mặc dù tôi chủ yếu làm Javascript), thông thường nó giống như một lỗi phát sinh lỗ hổng triển khai cơ bản đòi hỏi phải cơ cấu lại biểu đồ phụ thuộc / cuộc gọi của các bit bên trong khác nhau để đạt được một sửa chữa thực sự mạnh mẽ.
Ixrec 13/03/2016

5
"" sớm hay muộn, bạn sẽ tạo một lớp con của mọi lớp "gần như chắc chắn không phải là trường hợp." Và hơn thế nữa, bạn gần như chắc chắn sẽ không, và bạn NÊN chắc chắn không, ghi đè mọi chức năng trong một lớp và thay đổi cách sử dụng của mọi thành phần dữ liệu trong một lớp. Một số điều nên được viết bằng đá để lớp có ý nghĩa.
Jay

1
Tôi sẽ ném cho bạn nhiều hơn +1 nếu tôi có thể => Đó là những gì chúng tôi gọi là tiền thưởng @abluejelly
Thomas Ayoub

256

Trong OOP, sớm hay muộn, bạn sẽ tạo một lớp con của một lớp

Cái này sai. Không phải mọi lớp đều có nghĩa là được phân lớp và một số ngôn ngữ OOP được nhập tĩnh thậm chí có các tính năng để ngăn chặn nó, ví dụ: final(Java và C ++) hoặc sealed(C #).

thật tốt khi hiểu và có thể sửa đổi hoàn thành việc thực hiện.

Không, không phải vậy. Thật tốt khi một lớp có thể xác định rõ giao diện công khai của nó và bảo toàn các bất biến của nó ngay cả khi nó được kế thừa từ đó.

Nói chung, kiểm soát truy cập là về khoang. Bạn muốn một phần riêng biệt của mã được hiểu mà không cần phải hiểu chi tiết cách nó tương tác với phần còn lại của mã. Truy cập cá nhân cho phép điều đó. Nếu mọi thứ ít nhất được bảo vệ, bạn phải hiểu mọi lớp con làm gì để hiểu cách lớp cơ sở hoạt động.

Hoặc để đặt nó theo các điều khoản của Scott Meyers: các phần riêng tư của một lớp bị ảnh hưởng bởi một lượng mã hữu hạn: mã của chính lớp đó.

Các phần công khai có khả năng bị ảnh hưởng bởi mỗi bit mã tồn tại và mỗi bit mã chưa được viết, đây là một lượng mã vô hạn.

Các phần được bảo vệ có khả năng bị ảnh hưởng bởi mọi lớp con hiện có và mọi lớp con chưa được viết, đây cũng là một lượng mã vô hạn.

Kết luận là bảo vệ mang lại cho bạn rất ít so với công chúng, trong khi riêng tư mang lại cho bạn một sự cải thiện thực sự. Đó là sự tồn tại của trình xác định truy cập được bảo vệ là nghi vấn, không riêng tư.


34
+1 cho kịch bản lý thuyết về "bị ảnh hưởng bởi số lượng mã vô hạn"
Broken_Window 11/03/2016

13
Có lẽ đáng chú ý là các nhà thiết kế C # thực sự đã quyết định cấm ghi đè một phương thức được kế thừa trừ khi nó được đánh dấu rõ ràng là virtual, vì những lý do được nêu trong câu trả lời này.
Will Vousden 11/03/2016

11
Hoặc đặt đơn giản: protectedlà cho các phương thức, không phải cho các thành viên dữ liệu.
Matthieu M.

7
Bây giờ tôi không thể tìm thấy nó, nhưng tôi nhớ đã đọc một câu trả lời được viết tốt từ @EricLippert về lý do tại sao MS là sealedphần lớn của thư viện .net và IIRC tại sao anh ta lại thích khóa phần còn lại. Thời điểm bạn cho phép người thừa kế bên thứ 3 bắt đầu chạm vào các giá trị nội bộ, bạn cần thêm một lượng lớn kiểm tra xác thực / kiểm tra độ chính xác cho mọi phương thức vì bạn không còn có thể tin tưởng bất kỳ bất biến thiết kế nào về trạng thái bên trong của đối tượng.
Dan Neely

4
@ ThorbjørnRavnAndersen "trong khi phát triển nhưng không được phát hành" - làm thế nào một lớp học phải biết? Bạn có thực sự muốn biên dịch phiên bản thử nghiệm khác với phiên bản đã phát hành khi bạn thậm chí không thể kiểm tra phiên bản phát hành không? Các xét nghiệm không cần phải truy cập công cụ riêng tư nào.
Sebastian Redl

33

Vâng, các lĩnh vực tư nhân là hoàn toàn cần thiết. Chỉ trong tuần này tôi cần phải viết một triển khai từ điển tùy chỉnh nơi tôi kiểm soát những gì được đưa vào từ điển. Nếu trường từ điển được bảo vệ hoặc công khai, thì các điều khiển tôi viết rất cẩn thận có thể dễ dàng bị phá vỡ.

Các trường riêng thường là về việc cung cấp các biện pháp bảo vệ rằng dữ liệu giống như bộ mã hóa ban đầu dự kiến. Làm cho mọi thứ được bảo vệ / công khai và bạn cưỡi một huấn luyện viên và ngựa thông qua các thủ tục và xác nhận.


2
+1 cho "cưỡi xe ngựa và ngựa qua" bất cứ điều gì. Đó là một cụm từ tuyệt vời mà tôi muốn tôi nghe nhiều hơn.
Nic Hartley

2
Nếu ai đó cần phân lớp của bạn, có lẽ anh ta nên có quyền truy cập vào các biện pháp bảo vệ của bạn? Và nếu bạn không muốn ai đó thay đổi các biện pháp bảo vệ của mình để đạt được mục tiêu nào đó, có lẽ không nên chia sẻ mã? Nó hoạt động như thế này trong Ruby - private ít nhiều được khuyến nghị.
Adam Libuša

3
@ AdamLibuša "Đừng chia sẻ mã"? Ngay khi bạn xuất bản bất kỳ DLL nào, bạn đang chia sẻ mã - tất cả các cấu trúc và phương thức và mọi thứ đều có ở đó để cả thế giới nhìn thấy, đặc biệt là với các ngôn ngữ hỗ trợ phản chiếu theo mặc định. Mọi người đều có thể làm bất cứ điều gì họ muốn với "mã của bạn" - privatechỉ là một cách nói "không chạm vào những thứ này" và được thực thi trong hợp đồng biên dịch. Trong các hệ thống như .NET, điều này cũng có ý nghĩa bảo mật quan trọng - bạn chỉ có thể chạm vào quyền riêng tư của người khác khi bạn hoàn toàn tin tưởng (về cơ bản tương đương với quyền truy cập của quản trị viên / root).
Luaan

2
@ AdamLibuša Tôi nghĩ rằng sự nhầm lẫn của bạn chủ yếu bắt nguồn từ các cách tiếp cận OOP khác nhau mà các ngôn ngữ khác nhau đã sử dụng. Nguồn gốc của OOP (như được xác định ban đầu) là nhắn tin - điều đó có nghĩa là mọi thứ đều riêng tư, ngoại trừ các tin nhắn bạn trả lời. Trong hầu hết các ngôn ngữ OOPish, điều này được thể hiện là "giữ dữ liệu của bạn ở chế độ riêng tư" - một cách để làm cho giao diện công cộng nhỏ nhất có thể. Cách duy nhất mà người dùng (có thể là lớp con hoặc lớp khác) phải điều khiển lớp của bạn là thông qua giao diện chung mà bạn đã xác định - tương tự như cách bạn thường sử dụng vô lăng và bàn đạp để lái xe của mình :)
Luaan

3
@Luaan +1 khi bạn chạm vào tư nhân của người khác!
Nigel Touch

12

Khi cố gắng lý luận chính thức về tính đúng đắn của chương trình Hướng đối tượng , điển hình là sử dụng phương pháp mô đun liên quan đến bất biến đối tượng . Trong cách tiếp cận này

  1. Các phương pháp đã liên kết với chúng các điều kiện trước và sau (hợp đồng).
  2. Các đối tượng đã liên kết với chúng bất biến.

Lý luận mô-đun về một đối tượng tiến hành như sau (ít nhất là một xấp xỉ đầu tiên)

  1. Chứng minh rằng hàm tạo của đối tượng thiết lập bất biến
  2. Đối với mỗi phương thức không riêng tư, giả sử đối tượng bất biến và điều kiện tiên quyết của phương thức được giữ khi nhập, sau đó chứng minh rằng phần thân của mã ngụ ý rằng hậu điều kiện và bất biến giữ khi thoát phương thức

Hãy tưởng tượng rằng chúng tôi xác minh một object Acách sử dụng phương pháp trên. Và bây giờ muốn xác minh method gcủa object Btrong đó kêu gọi method fcủa object A. Lý luận mô đun cho phép chúng ta suy luận về method gmà không cần phải xem xét lại việc thực hiện method f. Với điều kiện chúng ta có thể thiết lập bất biến object Avà điều kiện tiên quyết method ftại trang web cuộc gọi, method g,chúng ta có thể lấy điều kiện bài đăng method flàm bản tóm tắt hành vi của cuộc gọi phương thức. Hơn nữa, chúng ta cũng sẽ biết rằng sau khi cuộc gọi trả về bất biến Avẫn giữ.

Mô-đun lý luận này là những gì cho phép chúng ta suy nghĩ chính thức về các chương trình lớn. Chúng ta có thể suy luận về từng phương thức riêng lẻ và sau đó tổng hợp kết quả của lý do này để lý giải về các phần lớn hơn của chương trình.

Các lĩnh vực tư nhân rất hữu ích trong quá trình này. Để biết rằng bất biến của một đối tượng tiếp tục giữ giữa hai lệnh gọi phương thức trên đối tượng đó, chúng ta thường dựa vào thực tế là đối tượng không được sửa đổi trong giai đoạn can thiệp.

Để lý luận mô đun hoạt động trong bối cảnh mà các đối tượng không có trường riêng thì chúng ta sẽ phải có một cách nào đó để đảm bảo rằng bất kỳ trường nào xảy ra được đặt bởi một đối tượng khác, rằng bất biến luôn được thiết lập lại (sau trường bộ). Thật khó để tưởng tượng một bất biến đối tượng mà cả hai đều nắm giữ cho dù các trường của đối tượng có giá trị gì và cũng hữu ích trong việc suy luận về tính đúng đắn của chương trình. Chúng tôi có lẽ sẽ phải phát minh ra một số quy ước phức tạp xung quanh truy cập trường. Và có lẽ cũng mất một số (thậm chí tệ nhất là tất cả) khả năng suy luận theo mô đun.

Lĩnh vực được bảo vệ

Các trường được bảo vệ khôi phục một số khả năng của chúng tôi để lập luận theo mô-đun. Tùy thuộc vào ngôn ngữ protectedcó thể hạn chế khả năng đặt trường cho tất cả các lớp con hoặc tất cả các lớp con và các lớp cùng gói. Nó thường là trường hợp chúng ta không có quyền truy cập vào tất cả các lớp con khi chúng ta suy luận về tính chính xác của một đối tượng chúng ta đang viết. Ví dụ: bạn có thể đang viết một thành phần hoặc thư viện mà sau này sẽ được sử dụng trong một chương trình lớn hơn (hoặc một số chương trình lớn hơn) - một số trong đó thậm chí có thể chưa được viết. Thông thường, bạn sẽ không biết nếu và theo cách nào nó có thể được phân loại.

Tuy nhiên, nó thường được đặt trên một lớp con để duy trì đối tượng bất biến của lớp mà nó mở rộng. Vì vậy, trong ngôn ngữ bảo vệ chỉ có nghĩa là "lớp phụ" và ở đó chúng tôi bị kỷ luật để đảm bảo rằng các lớp con luôn duy trì sự bất biến của siêu lớp của chúng, bạn có thể lập luận rằng lựa chọn sử dụng được bảo vệ thay vì riêng tư chỉ mất tính mô đun hóa tối thiểu .

Mặc dù tôi đã nói về lý luận chính thức, người ta thường nghĩ rằng khi các lập trình viên lý do không chính thức về tính chính xác của mã của họ, đôi khi họ cũng dựa vào các loại đối số tương tự.


8

privatecác biến trong một lớp tốt hơn so protectedvới cùng một lý do là một breakcâu lệnh bên trong một switchkhối tốt hơn một goto labelcâu lệnh; đó là các lập trình viên của con người dễ bị lỗi.

protectedcác biến cho vay để lạm dụng không cố ý (lỗi lập trình viên), giống như gototuyên bố cho vay để tạo mã spaghetti.

Có thể viết mã không có lỗi bằng cách sử dụng protectedcác biến lớp không? Phải, tất nhiên! Cũng giống như có thể viết mã không có lỗi hoạt động bằng cách sử dụng goto; nhưng như sáo ngữ "Chỉ vì bạn có thể, không có nghĩa là bạn nên!"

Các lớp, và thực sự là mô hình OO, tồn tại để bảo vệ chống lại các lập trình viên con người dễ mắc lỗi, mắc lỗi. Việc phòng thủ chống lại sai lầm của con người chỉ tốt như các biện pháp phòng thủ được xây dựng trong lớp. Làm cho việc thực hiện lớp học của bạn protectedtương đương với việc thổi một lỗ hổng lớn vào các bức tường của một pháo đài.

Các lớp cơ sở hoàn toàn không có kiến ​​thức về các lớp dẫn xuất. Đối với một lớp cơ sở có liên quan, protectedthực tế không cung cấp cho bạn sự bảo vệ nào nhiều hơn public, bởi vì không có gì ngăn cản một lớp dẫn xuất tạo ra một publicgetter / setter hoạt động như một cửa hậu.

Nếu một lớp cơ sở cho phép truy cập không bị cản trở vào các chi tiết triển khai bên trong của nó, thì chính lớp đó sẽ không thể bảo vệ chống lại các sai lầm. Các lớp cơ sở hoàn toàn không có kiến ​​thức về các lớp dẫn xuất của chúng, và do đó không có cách nào bảo vệ chống lại các sai lầm trong các lớp dẫn xuất đó.

Điều tốt nhất mà lớp cơ sở có thể làm là che giấu càng nhiều việc triển khai càng tốt privatevà đặt đủ các hạn chế để bảo vệ chống lại các thay đổi từ các lớp dẫn xuất hoặc bất kỳ thứ gì khác bên ngoài lớp.

Cuối cùng, các ngôn ngữ cấp cao tồn tại để giảm thiểu lỗi của con người. Thực hành lập trình tốt (như nguyên tắc RẮN ) cũng tồn tại để giảm thiểu lỗi của con người.

Các nhà phát triển phần mềm bỏ qua các thực tiễn lập trình tốt có khả năng thất bại cao hơn nhiều và có nhiều khả năng tạo ra các giải pháp không thể nhầm lẫn. Những người tuân theo các thực hành tốt có cơ hội thất bại thấp hơn nhiều, và có nhiều khả năng tạo ra các giải pháp bảo trì làm việc.


Để downvoter - những gì có thể được thay đổi / cải thiện về câu trả lời này?
Ben Cottrell

Tôi đã không bỏ phiếu, nhưng so sánh mức độ truy cập với break / goto?
imel96

@ imel96 Không, so sánh lý do tại sao chúng được tránh và không được khuyến khích bởi "Thực tiễn tốt nhất" (đặc biệt là để viết mã mới ). tức là một lập trình viên có năng lực sẽ tránh các publicchi tiết triển khai vì nó cho vay mã không thể nhầm lẫn. Một lập trình viên có năng lực sẽ tránh gotobởi vì nó cho vay mã không thể nhầm lẫn . Tuy nhiên, thế giới thực đôi khi bạn bị lạc trong một mớ hỗn độn mã di sản khủng khiếp và không có lựa chọn nào khác ngoài việc sử dụng goto, và vì lý do đó, đôi khi bạn không có lựa chọn nào khác ngoài sử dụng các chi tiết triển khai công khai / được bảo vệ.
Ben Cottrell

Mức độ nghiêm trọng khác nhau, nó không thể so sánh được. Tôi có thể sống với mã chỉ có publicthuộc tính / giao diện, nhưng không có mã chỉ sử dụng goto.
imel96

2
@ imel96 Bạn đã bao giờ thực sự làm việc trong mã sử dụng gotohay chỉ vì bạn đã đọc các bài viết như vậy? Tôi hiểu tại sao goto là xấu xa vì tôi đã dành 2 năm làm việc trong mã cổ đại sử dụng nó; và tôi đã dành nhiều thời gian hơn để làm việc trong mã trong đó "các lớp" có các chi tiết triển khai của chúng bị rò rỉ ở mọi nơi publicvà các công friendcụ chỉ định được sử dụng như các bản hack nhanh / dễ / bẩn. Tôi không chấp nhận lập luận rằng "phơi bày công khai các chi tiết triển khai" không thể gây ra một mớ hỗn độn spaghetti đan xen ở cùng mức độ nghiêm trọng như gotovì nó hoàn toàn có thể.
Ben Cottrell

4

Các lớp kế thừa có hai hợp đồng - một với các chủ sở hữu các tham chiếu đối tượng và với các lớp dẫn xuất. Các thành viên công cộng bị ràng buộc bởi hợp đồng với chủ sở hữu tham chiếu và các thành viên được bảo vệ bị ràng buộc bởi hợp đồng với các lớp xuất phát.

Làm cho các thành viên protectedlàm cho nó trở thành một lớp cơ sở linh hoạt hơn, nhưng thường sẽ giới hạn các cách mà các phiên bản tương lai của lớp có thể thay đổi. Làm cho các thành viên privatecho phép tác giả lớp linh hoạt hơn để thay đổi hoạt động bên trong của lớp, nhưng giới hạn các loại lớp có thể có nguồn gốc hữu ích từ nó.

Ví dụ, List<T>trong .NET làm cho cửa hàng sao lưu riêng tư; nếu nó được bảo vệ, các loại dẫn xuất có thể làm một số điều hữu ích mà không thể có được, nhưng các phiên bản tương lai của List<T>mãi mãi sẽ phải sử dụng cửa hàng sao lưu nguyên khối cồng kềnh của nó ngay cả đối với các danh sách chứa hàng triệu mặt hàng. Làm cho cửa hàng sao lưu riêng tư sẽ cho phép các phiên bản trong tương lai List<T>sử dụng cửa hàng sao lưu hiệu quả hơn mà không phá vỡ các lớp dẫn xuất.


4

Tôi nghĩ có một giả định chính trong lập luận của bạn rằng khi ai đó viết một lớp, họ không biết ai có thể mở rộng lớp đó xuống đường và vì lý do gì . Giả định này, lập luận của bạn sẽ có ý nghĩa hoàn hảo bởi vì mọi biến bạn đặt ở chế độ riêng tư sau đó có khả năng cắt đứt một số con đường phát triển. Tuy nhiên, tôi sẽ từ chối giả định đó.

Nếu giả định đó bị bác bỏ thì chỉ có hai trường hợp cần xem xét.

  1. Tác giả của lớp ban đầu đã có những ý tưởng rất rõ ràng về lý do tại sao nó có thể được mở rộng (ví dụ: đó là BaseFoo và sẽ có một vài triển khai Foo cụ thể trên đường).

Trong trường hợp này, tác giả biết rằng ai đó sẽ mở rộng lớp học và tại sao và do đó sẽ biết chính xác những gì cần bảo vệ và những gì để riêng tư. Họ đang sử dụng phân biệt riêng tư / được bảo vệ để giao tiếp một giao diện sắp xếp cho người dùng tạo lớp con.

  1. Tác giả của lớp con đang cố gắng hack một số hành vi vào lớp cha.

Trường hợp này rất hiếm (bạn có thể cho rằng nó không hợp pháp) và không được ưu tiên chỉ sửa đổi lớp gốc trong cơ sở mã gốc. Nó cũng có thể là một triệu chứng của thiết kế xấu. Trong những trường hợp đó, tôi thích người hack trong hành vi chỉ sử dụng các cách hack khác như bạn bè (C / C ++) và setAccessible(true)(Java).

Tôi nghĩ rằng nó là an toàn để từ chối giả định đó.

Điều này thường rơi trở lại ý tưởng về thành phần trên thừa kế . Kế thừa thường được dạy như một cách lý tưởng để giảm việc tái sử dụng mã, tuy nhiên nó hiếm khi là lựa chọn đầu tiên để tái sử dụng mã. Tôi không có một cuộc tranh luận đơn giản và nó có thể là một vấn đề khá khó hiểu và gây tranh cãi. Tuy nhiên, theo kinh nghiệm của tôi với mô hình hóa miền, tôi đã thấy rằng tôi hiếm khi sử dụng tính kế thừa mà không hiểu rõ về ai sẽ kế thừa lớp của tôi và tại sao.


2

Tất cả ba cấp độ truy cập đều có trường hợp sử dụng của chúng, OOP sẽ không hoàn chỉnh nếu thiếu bất kỳ trong số chúng. Thông thường bạn làm

  • làm cho tất cả các biến / dữ liệu thành viên riêng tư . Bạn không muốn ai đó từ bên ngoài gây rối với dữ liệu nội bộ của mình. Ngoài ra các phương pháp cung cấp chức năng phụ trợ (nghĩ các tính toán dựa trên một số biến thành viên) cho giao diện công khai hoặc được bảo vệ của bạn - đây chỉ là để sử dụng nội bộ và bạn có thể muốn thay đổi / cải thiện nó trong tương lai.
  • làm cho giao diện chung của lớp của bạn công khai . Đó là những gì người dùng của lớp ban đầu của bạn phải làm việc với, và cách bạn nghĩ rằng các lớp dẫn xuất cũng sẽ trông như thế nào. Để cung cấp đóng gói thích hợp, đây thường chỉ là các phương thức (và các lớp / cấu trúc trình trợ giúp, enums, typedefs, bất cứ điều gì người dùng cần để làm việc với các phương thức của bạn), không phải là các biến.
  • tuyên bố các phương thức được bảo vệ có thể được sử dụng cho ai đó muốn mở rộng / chuyên môn hóa chức năng của lớp bạn, nhưng không nên là một phần của giao diện công cộng - thực tế bạn thường nâng các thành viên riêng lên để bảo vệ khi cần thiết. Nếu nghi ngờ bạn không, cho đến khi bạn biết rằng
    1. lớp của bạn có thể / có thể / sẽ được phân lớp,
    2. và có một ý tưởng rõ ràng các trường hợp sử dụng của phân lớp có thể là gì.

Và bạn đi chệch khỏi kế hoạch chung này chỉ khi có lý do chính đáng ™ . Cảnh giác với "điều này sẽ làm cho cuộc sống của tôi dễ dàng hơn khi tôi có thể tự do truy cập nó từ bên ngoài" (và bên ngoài ở đây cũng bao gồm các lớp con). Khi tôi thực hiện phân cấp lớp, tôi thường bắt đầu với các lớp không có thành viên được bảo vệ, cho đến khi tôi đến phân lớp / mở rộng / chuyên môn hóa chúng, trở thành các lớp cơ sở của khung / bộ công cụ và đôi khi chuyển một phần chức năng ban đầu của chúng lên một cấp.


1

Một câu hỏi thú vị hơn, có lẽ, là tại sao bất kỳ loại lĩnh vực nào khác ngoài tư nhân là cần thiết. Khi một lớp con cần tương tác với dữ liệu của siêu lớp, làm như vậy trực tiếp tạo ra một khớp nối trực tiếp giữa hai lớp, trong khi sử dụng các phương thức để cung cấp cho sự tương tác giữa hai lớp cho phép mức độ có thể tạo ra sự thay đổi đối với siêu lớp mà nếu không sẽ rất khó khăn.

Một số ngôn ngữ (ví dụ: Ruby và Smalltalk) không cung cấp các trường công khai để các nhà phát triển không khuyến khích cho phép ghép trực tiếp với các triển khai lớp của họ, nhưng tại sao không đi xa hơn và chỉ có các trường riêng? Sẽ không mất tính tổng quát (vì siêu lớp luôn có thể cung cấp các bộ truy cập được bảo vệ cho lớp con), nhưng nó sẽ đảm bảo rằng các lớp luôn có ít nhất một mức độ cách ly nhỏ với các lớp con của chúng. Tại sao đây không phải là một thiết kế phổ biến hơn?


1

Rất nhiều câu trả lời hay ở đây, nhưng dù sao tôi cũng sẽ ném vào mình hai xu. :-)

Riêng tư là tốt cho cùng một lý do rằng dữ liệu toàn cầu là xấu.

Nếu một lớp khai báo dữ liệu riêng tư, thì bạn hoàn toàn biết rằng mã duy nhất gây rối với dữ liệu này là mã trong lớp. Khi có lỗi, bạn không phải tìm kiếm trên tất cả các sáng tạo để tìm mọi nơi có thể thay đổi dữ liệu này. Bạn biết nó trong lớp học. Khi bạn thực hiện thay đổi mã và bạn thay đổi điều gì đó về cách sử dụng trường này, bạn không phải theo dõi tất cả các địa điểm tiềm năng có thể sử dụng trường này và nghiên cứu xem thay đổi theo kế hoạch của bạn có phá vỡ chúng không. Bạn biết những nơi duy nhất là trong lớp.

Tôi đã nhiều lần phải thay đổi các lớp trong thư viện và được sử dụng bởi nhiều ứng dụng và tôi phải cẩn thận để đảm bảo rằng tôi không phá vỡ một số ứng dụng mà tôi không biết gì. Càng có nhiều dữ liệu công khai và được bảo vệ, càng có nhiều khả năng gặp rắc rối.


1

Tôi nghĩ rằng nó đáng để đề cập đến một số ý kiến ​​bất đồng.

Về lý thuyết, thật tốt khi kiểm soát mức truy cập vì tất cả các lý do được đề cập trong các câu trả lời khác.

Trong thực tế, quá thường xuyên khi xem xét mã, tôi thấy mọi người (những người thích sử dụng riêng tư), thay đổi cấp độ truy cập từ riêng tư -> được bảo vệ và không quá thường xuyên từ được bảo vệ -> công khai. Hầu như luôn luôn, thay đổi thuộc tính lớp liên quan đến sửa đổi setters / getters. Những thứ này đã lãng phí phần lớn thời gian của tôi (xem lại mã) và của họ (thay đổi mã).

Nó cũng làm tôi khó chịu vì điều đó có nghĩa là các lớp của họ không được đóng để sửa đổi.

Đó là với mã nội bộ nơi bạn luôn có thể thay đổi nó nếu bạn cũng cần. Tình hình tồi tệ hơn với mã bên thứ 3 khi việc thay đổi mã không quá dễ dàng.

Vì vậy, có bao nhiêu lập trình viên nghĩ rằng đó là một rắc rối? Chà, có bao nhiêu người đang sử dụng các ngôn ngữ lập trình không có riêng tư? Tất nhiên, mọi người không chỉ sử dụng các ngôn ngữ đó vì họ không có các nhà đầu cơ riêng tư, nhưng nó giúp đơn giản hóa các ngôn ngữ và sự đơn giản là rất quan trọng.

Imo nó rất giống với kiểu gõ động / tĩnh. Về lý thuyết, gõ tĩnh là rất tốt. Trong thực tế, nó chỉ ngăn như 2% lỗi Hiệu quả không hợp lý của Dynamic Typing ... . Sử dụng riêng tư có thể ngăn ngừa lỗi ít hơn thế.

Tôi nghĩ rằng các nguyên tắc RẮN là tốt, tôi mong mọi người quan tâm đến chúng hơn là họ quan tâm đến việc tạo ra một lớp học với công chúng, được bảo vệ và riêng tư.


1
Nếu bạn cần thay đổi mã của bên thứ 3 khi sử dụng nó, thì nó không được thiết kế tốt, hoặc bạn không sử dụng lại nó tốt. Bạn luôn có thể sử dụng lại một lớp không trừu tượng bằng cách gói gọn nó, nhưng trong thực tế, bạn hầu như không cần phải phân lớp nó.
Little Santi

0

Tôi cũng muốn thêm một ví dụ thực tế khác về lý do tại sao protectedkhông đủ. Tại trường đại học của tôi, những năm đầu tiên thực hiện một dự án nơi họ phải phát triển phiên bản máy tính để bàn của một trò chơi cờ (mà sau này AI được phát triển và nó được kết nối với những người chơi khác qua mạng). Một số mã một phần được cung cấp cho họ để bắt đầu sử dụng chúng bao gồm khung kiểm tra. Một số thuộc tính của lớp trò chơi chính được đưa ra protectedđể các lớp thử nghiệm mở rộng lớp này có quyền truy cập vào chúng. Nhưng những lĩnh vực này không phải là thông tin nhạy cảm.

Là một TA cho đơn vị, tôi thường thấy các sinh viên chỉ đơn giản là tạo ra tất cả các mã được thêm vào của họ protectedhoặc public(có lẽ vì họ đã thấy các thứ khác protectedvà các publicthứ khác và cho rằng họ nên tuân theo). Tôi hỏi họ tại sao mức độ bảo vệ của họ không phù hợp và nhiều người không biết tại sao. Câu trả lời là thông tin nhạy cảm mà chúng tiếp xúc với các lớp con có nghĩa là một người chơi khác có thể gian lận bằng cách mở rộng lớp đó và truy cập thông tin có độ nhạy cao cho trò chơi (về cơ bản là vị trí ẩn của đối thủ, tôi đoán tương tự như thế nào nếu bạn có thể thấy các mảnh đối thủ của bạn trên bảng tàu chiến bằng cách mở rộng một số lớp). Điều đó làm cho mã của họ rất nguy hiểm trong bối cảnh của trò chơi.

Ngoài ra, có nhiều lý do khác để giữ một cái gì đó riêng tư cho ngay cả các lớp con của bạn. Có thể là để che giấu các chi tiết triển khai có thể làm rối tung hoạt động chính xác của lớp nếu bị thay đổi bởi một người không nhất thiết phải biết họ đang làm gì (chủ yếu nghĩ về người khác sử dụng mã của bạn ở đây).


1
Tôi không hiểu Trừ khi người chơi đang tự động mở rộng các lớp trong khi trò chơi đang chạy, làm thế nào điều này có thể được sử dụng để gian lận? Điều này có nghĩa là bạn đang nhập mã không tin cậy vào cơ sở mã của mình. Nếu bạn nói rằng bạn cho sinh viên biên soạn các lớp và ngăn họ gian lận trong bài tập của họ bằng cách sửa đổi chi tiết thực hiện, thì việc đặt mức bảo vệ sẽ không khiến bài tập trở nên an toàn hơn. Học sinh có thể dịch ngược thư viện hoặc sử dụng sự phản chiếu để lợi thế của họ. Điều này tất nhiên phụ thuộc vào mức độ thực thi mà bạn đang theo đuổi.
Sam

@sam Nói chung mã phải được viết với giả định rằng bất kỳ ai sửa đổi nó tiếp theo sẽ không cần biết toàn bộ cơ sở mã bên ngoài. Đó thậm chí có thể là tác giả ban đầu (thời gian trôi qua, thiếu ngủ ...). Gian lận có thể là sinh viên được cung cấp mã bộ xương để xác thịt và thay đổi logic họ không nên chạm vào.
Phil Lello

0

Các phương thức / biến riêng tư thường sẽ bị ẩn khỏi một lớp con. Đó có thể là một điều tốt.

Một phương thức riêng tư có thể đưa ra các giả định về các tham số và để lại sự kiểm tra độ tỉnh táo cho người gọi.

Một phương pháp được bảo vệ nên kiểm tra đầu vào.


-1

"Riêng tư" có nghĩa là: Không có ý định thay đổi hoặc truy cập bởi bất kỳ ai ngoại trừ chính lớp đó. Không có ý định được thay đổi hoặc truy cập bởi các lớp con. Phân lớp? Những lớp con nào? Bạn không cần phải phân lớp này!

"được bảo vệ" có nghĩa là: Chỉ dự định được thay đổi hoặc truy cập bởi các lớp hoặc lớp con. Có khả năng khấu trừ rằng bạn phải phân lớp, nếu không thì tại sao "được bảo vệ" và không "riêng tư"?

Có một sự khác biệt rõ ràng ở đây. Nếu tôi làm một cái gì đó riêng tư, bạn có nghĩa vụ phải giữ những ngón tay bẩn thỉu của bạn khỏi nó. Ngay cả khi bạn là một lớp con.


-1

Một trong những điều đầu tiên tôi làm khi tôi phân lớp một lớp là thay đổi một loạt các phương thức riêng tư để được bảo vệ

Một số lý do về privateso với protected phương pháp :

privatephương pháp ngăn chặn tái sử dụng mã. Một lớp con không thể sử dụng mã trong phương thức riêng tư và có thể phải thực hiện lại - hoặc thực hiện lại (các) phương thức ban đầu phụ thuộc vào phương thức riêng tư & c.

Mặt khác, bất kỳ phương thức nào không private thể được xem là API do lớp cung cấp cho "thế giới bên ngoài", theo nghĩa là các lớp con bên thứ ba cũng được coi là "thế giới bên ngoài", như một người khác đề xuất trong câu trả lời của anh ấy đã sẵn sàng.

Đó có phải là một điều xấu? - Tôi không nghĩ vậy.

Tất nhiên, API công khai (giả) khóa lập trình viên gốc và cản trở việc tái cấu trúc các giao diện đó. Nhưng nhìn theo một cách khác, tại sao một lập trình viên không nên thiết kế "chi tiết triển khai" của riêng mình theo cách sạch sẽ và ổn định như API công khai của mình? Anh ta có nên sử dụng privateđể anh ta có thể cẩu thả trong việc cấu trúc mã "riêng tư" của mình không? Suy nghĩ có lẽ anh ta có thể làm sạch nó sau, bởi vì sẽ không có ai chú ý? - Không.

Lập trình viên cũng nên suy nghĩ một chút về mã "riêng tư" của mình, để cấu trúc nó theo cách cho phép hoặc thậm chí thúc đẩy việc tái sử dụng càng nhiều mã càng tốt ở nơi đầu tiên. Sau đó, những phần không riêng tư có thể không trở thành gánh nặng trong tương lai như một số nỗi sợ hãi.

Rất nhiều mã (khung) tôi thấy thông qua việc sử dụng không nhất quán private: protected, các phương thức không phải là cuối cùng mà hầu như không làm gì hơn là ủy thác cho một phương thức riêng tư thường được tìm thấy. protected, các phương thức không phải là cuối cùng mà hợp đồng chỉ có thể được thực hiện thông qua truy cập trực tiếp vào các lĩnh vực tư nhân.

Các phương thức này không thể được ghi đè / tăng cường một cách hợp lý, mặc dù về mặt kỹ thuật không có gì để làm cho điều đó (trình biên dịch-) rõ ràng.

Muốn mở rộng và kế thừa? Đừng làm phương pháp của bạn private.

Không muốn hành vi nhất định của lớp bạn bị thay đổi? Thực hiện các phương pháp của bạn final.

Thực sự không thể có phương pháp của bạn được gọi bên ngoài một bối cảnh nhất định, được xác định rõ? Làm cho phương thức của bạn privatevà / hoặc suy nghĩ về cách bạn có thể làm cho bối cảnh được xác định rõ có sẵn để sử dụng lại thông qua một protectedphương thức trình bao bọc khác .

Đó là lý do tại sao tôi ủng hộ việc sử dụng một privatecách tiết kiệm. Và để không nhầm lẫn privatevới final. - Nếu việc triển khai một phương thức là quan trọng đối với hợp đồng chung của lớp và do đó không được thay thế / ghi đè, hãy thực hiện nó final!

Đối với các lĩnh vực, privatekhông thực sự xấu. Miễn là (các) trường có thể được "sử dụng" một cách hợp lý thông qua các phương thức thích hợp ( không phải getXX() hoặc setXX()!).


2
-1 Điều này không giải quyết được câu hỏi privateso với protectedcâu hỏi ban đầu, đưa ra lời khuyên sai lầm ("sử dụng một privatecách tiết kiệm"?) Và đi ngược lại sự đồng thuận chung về thiết kế OO. Sử dụng privatekhông có gì để làm với việc ẩn mã cẩu thả.
Andres F.

3
Bạn đề cập rằng một lớp có API, nhưng dường như không tạo ra kết nối mà người dùng API không muốn bị gánh nặng với các chi tiết mà họ không cần biết. Tình huống lý tưởng cho người dùng của một lớp là giao diện chỉ chứa các phương thức họ cần và không có gì khác. Làm các phương thức privategiúp giữ sạch API.
Ben Cottrell

1
@HannoBinder Tôi đồng ý rằng có rất nhiều lớp học bị cứng nhắc, tuy nhiên tính linh hoạt và tái sử dụng mã ít quan trọng hơn nhiều so với đóng gói và khả năng duy trì lâu dài. Xem xét trường hợp của bạn trong đó tất cả các lớp có các thành viên được bảo vệ và các lớp dẫn xuất có thể gây rối với bất kỳ chi tiết triển khai nào họ muốn; Làm thế nào để bạn kiểm tra đơn vị một cách đáng tin cậy các lớp đó khi biết rằng bất kỳ phần nào trong bất kỳ phần nào của mã chưa được đặt mã có thể khiến mã đó bị lỗi bất cứ lúc nào? Có bao nhiêu "hack" như vậy mã có thể mất trước khi tất cả biến thành một quả bóng lớn?
Ben Cottrell

2
Oh, bạn nói "thành viên". Những gì tôi đang nói chỉ đề cập đến các phương pháp . Các biến thành viên (các trường) là một câu chuyện khác nhau, trong đó quyền riêng tư hợp lý hơn nhiều.
JimmyB

2
Danh pháp trong cuộc thảo luận ở khắp mọi nơi vì nó không cụ thể cho một ngôn ngữ cụ thể. "Thành viên" trong C ++ có thể là dữ liệu, chức năng, loại hoặc mẫu.
JDługosz

-1

Bạn có biết về một trường hợp sử dụng quan trọng trong đó riêng tư thay vì được bảo vệ là một công cụ tốt hoặc hai tùy chọn "được bảo vệ & công khai" có đủ cho các ngôn ngữ OOP không?

Riêng tư : khi bạn có thứ gì đó sẽ không bao giờ hữu ích cho bất kỳ lớp con nào để gọi hoặc ghi đè.

Được bảo vệ : khi bạn có một cái gì đó có một triển khai / hằng số cụ thể của lớp con.

Một ví dụ:

public abstract Class MercedesBenz() extends Car {
  //Might be useful for subclasses to know about their customers
  protected Customer customer; 

  /* Each specific model has its own horn. 
     Therefore: protected, so that each subclass might implement it as they wish
  */
  protected abstract void honk();

  /* Taken from the car class. */
  @Override
  public void getTechSupport(){
     showMercedesBenzHQContactDetails(customer);
     automaticallyNotifyLocalDealer(customer);
  }

  /* 
     This isn't specific for any subclass.
     It is also not useful to call this from inside a subclass,
     because local dealers only want to be notified when a 
     customer wants tech support. 
   */
  private void automaticallyNotifyLocalDealer(){
    ...
  }
}

2
khi bạn có thứ gì đó sẽ không bao giờ hữu ích cho bất kỳ lớp con nào để gọi hoặc ghi đè - Và đây là thứ mà bạn, theo tôi, không bao giờ biết trước.
Adam Libuša 18/03/2016

Chà, tôi cũng có thể cần phải nhấn nút đặt lại trên CMOS của mình. Nhưng giả định mặc định là tôi sẽ không thực sự cần nó, và do đó nó được đặt trong khung máy. Các phương pháp cũng vậy. Bạn làm cho nó được bảo vệ nếu lớp con thực sự cần phải thực hiện lại / gọi nó (xem: bấm còi). Bạn công khai nếu các bên khác cần gọi nó.
Arnab Datta

-2

Thật khó cho tôi để hiểu vấn đề này, vì vậy tôi muốn chia sẻ một phần kinh nghiệm của tôi:

  • Là gì bảo vệ lĩnh vực? Không có gì khác hơn một lĩnh vực, không thể được truy cập bên ngoài một lớp, tức là công khai như thế này : $classInstance->field. Và mẹo rằng "đây là nó". Trẻ em của lớp bạn sẽ có quyền truy cập đầy đủ vào nó, bởi vì đó là phần bên trong hợp pháp của chúng.
  • Là gì riêng lĩnh vực? Đó là một " sự riêng tư thực sự " cho lớp rất riêng của bạn việc bạn thực hiện lớp này. "Để xa tầm tay trẻ em", giống như trên chai thuốc. Bạn sẽ có một đảm bảo rằng nó không thể vượt qua bởi các dẫn xuất của lớp bạn, các phương thức của bạn - khi được gọi - sẽ có chính xác những gì bạn đã khai báo

CẬP NHẬT: một ví dụ thực tế bởi một nhiệm vụ thực tế tôi đã giải quyết. Đây là: bạn có mã thông báo, như USB hoặc LPT (đó là trường hợp của tôi) và bạn có một phần mềm trung gian. Mã thông báo yêu cầu bạn cung cấp mã pin, mở ra nếu đúng và bạn có thể gửi phần được mã hóa và một số khóa để giải mã. Các khóa được lưu trữ trong mã thông báo, bạn không thể đọc chúng, chỉ sử dụng chúng. Và có các khóa tạm thời cho một phiên, được ký bởi một khóa trong mã thông báo, nhưng được lưu trữ trong chính phần mềm trung gian. Khóa tạm thời không được phép rò rỉ ra bên ngoài, chỉ tồn tại ở cấp độ trình điều khiển. Và tôi đã sử dụng một trường riêng để lưu trữ khóa tạm thời này và một số dữ liệu liên quan đến kết nối phần cứng. Vì vậy, không có dẫn xuất nào có thể sử dụng không chỉ giao diện công cộng mà còn được bảo vệCác chương trình con "tiện dụng" mà tôi đã thực hiện cho một nhiệm vụ, nhưng không thể mở hộp mạnh với các phím và tương tác CTNH. Có ý nghĩa?


xin lỗi các bạn! chỉ làm rối họ lên - cập nhật câu trả lời của tôi =) CẢM ƠN! Tôi đã vội vàng và lầm tưởng =)
Alexey Vesnin

2
Tôi nghĩ rằng OP nhận thức được sự khác biệt giữa hai cấp độ bảo vệ, nhưng quan tâm đến lý do tại sao nên sử dụng một cấp độ khác.
Sam

@Sam hãy đọc sâu hơn câu trả lời của tôi. Họ là những loại lĩnh vực hoàn toàn khác nhau! điểm chung duy nhất của họ là cả hai không thể được giới thiệu công khai
Alexey Vesnin

2
Tôi không chắc chắn những gì bạn đang đề cập đến. Tôi nhận thức được sự khác biệt giữa riêng tư và được bảo vệ. Có lẽ bạn hiểu sai những gì tôi có nghĩa là cấp độ bảo vệ? Tôi đang đề cập đến 'cấp độ truy cập. Tôi đã cố gắng chỉ ra rằng trong khi câu trả lời của bạn không tệ, thì nó không trực tiếp giải quyết câu hỏi. OP dường như biết cả nghĩa được bảo vệ / riêng tư / công khai, nhưng không chắc chắn trong trường hợp nào họ muốn chọn cái này hơn cái kia. Đây có thể là cách bạn được bình chọn (không phải bởi tôi).
Sam

@Sam Tôi nghĩ rằng tôi đã có quan điểm của bạn - đã thêm một trường hợp thực tế từ kinh nghiệm / thực tế thực tế của tôi. Có phải những gì còn thiếu trên quan điểm của bạn?
Alexey Vesnin
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.