Riêng tư và được bảo vệ - Mối quan tâm thực hành tốt về khả năng hiển thị [đã đóng]


145

Tôi đã tìm kiếm và tôi biết sự khác biệt về lý thuyết.

  • công khai - Bất kỳ lớp / chức năng nào cũng có thể truy cập phương thức / thuộc tính.
  • được bảo vệ - Chỉ có lớp này và bất kỳ lớp con nào có thể truy cập phương thức / thuộc tính.
  • riêng tư - Chỉ lớp này có thể truy cập phương thức / thuộc tính. Nó thậm chí sẽ không được thừa kế.

Đó là tất cả tốt và tốt, câu hỏi là, sự khác biệt thực tế giữa chúng là gì? Khi nào bạn sẽ sử dụng privatevà khi nào bạn sẽ sử dụng protected? Có một thực hành tốt tiêu chuẩn hoặc chấp nhận được trên cái này?

Cho đến bây giờ, để giữ lại khái niệm kế thừa và đa hình, tôi sử dụng publiccho mọi thứ nên được truy cập từ bên ngoài (như hàm tạo và chức năng lớp chính) và protectedcho các phương thức bên trong (logic, phương thức trợ giúp, v.v.). Có phải tôi đang trên đường ray bên phải không?

(Lưu ý rằng câu hỏi này là dành cho tôi, nhưng cũng để tham khảo trong tương lai vì tôi chưa thấy câu hỏi nào giống như câu hỏi này).


33
Có vấn đề gì không? Bất kỳ ngôn ngữ nào có hỗ trợ OOP đều có mối quan tâm này. Tôi tình cờ lập trình trong PHP, nhưng tôi nghĩ câu hỏi áp dụng cho bất kỳ ngôn ngữ hỗ trợ OOP nào.
Madara's Ghost

2
Được rồi đủ công bằng, chỉ cần tự hỏi nếu bạn đã quên gắn thẻ. Bây giờ tôi thấy thẻ oop.
Paul Bellora

Tôi phải đặt mức độ hiển thị (riêng tư / công khai / được bảo vệ) cho mỗi và mọi tài sản của lớp? Hoặc chỉ một số trong số họ cần phải có một loại tầm nhìn? Nếu có, làm thế nào để quyết định tài sản nào cần phải có tầm nhìn được đặt ở đầu lớp?
dùng4271704

1
trái tay, sự khác biệt giữa bảo vệ và riêng tư dường như rõ ràng. Sử dụng được bảo vệ nếu các lớp con sẽ sử dụng phương thức / biến, nếu không thì sử dụng private. Cụ thể, nếu các lớp con sẽ phải xác định lại một biến riêng rất giống nhau trong phần cha mẹ, chỉ cần làm cho nó được bảo vệ.
iPherian

Có một công cụ xác định truy cập khác internalbằng ngôn ngữ C # giới hạn mức truy cập của một lớp hoặc các thành viên của nó trong một tổ hợp vật lý. Mặc dù vậy, tôi không chắc về sự hỗ trợ của nó hoặc một cái gì đó tương tự trong các ngôn ngữ khác.
RBT

Câu trả lời:


128

Không, bạn không đi đúng hướng. Một nguyên tắc tốt là: làm cho mọi thứ riêng tư nhất có thể. Điều này làm cho lớp của bạn được đóng gói nhiều hơn và cho phép thay đổi các phần bên trong của lớp mà không ảnh hưởng đến mã sử dụng lớp của bạn.

Nếu bạn thiết kế lớp của mình để có thể kế thừa, thì hãy cẩn thận chọn những gì có thể bị ghi đè và có thể truy cập được từ các lớp con và làm cho lớp đó được bảo vệ (và cuối cùng, nói về Java, nếu bạn muốn làm cho nó có thể truy cập được nhưng không bị ghi đè). Nhưng hãy lưu ý rằng, ngay khi bạn chấp nhận có các lớp con của lớp và có một trường hoặc phương thức được bảo vệ, thì trường hoặc phương thức này là một phần của API công khai của lớp và không thể thay đổi sau mà không phá vỡ các lớp con.

Một lớp không có ý định kế thừa nên được hoàn thành cuối cùng (bằng Java). Bạn có thể nới lỏng một số quy tắc truy cập (riêng tư để được bảo vệ, cuối cùng đến không cuối cùng) vì mục đích kiểm tra đơn vị, nhưng sau đó ghi lại nó và làm rõ rằng mặc dù phương thức này được bảo vệ, nhưng nó không bị ghi đè.


1
Câu hỏi thực sự ở đây là về privatevs protectedKhi nào tôi muốn một tài sản được thừa kế, và khi nào thì tôi không? Tôi thực sự không thể biết liệu đôi khi người dùng trong tương lai có muốn tham gia lớp học của tôi và mở rộng nó không ...
Madara's Ghost

16
Vâng, câu hỏi không phải là những gì người dùng muốn ghi đè, câu hỏi là những gì bạn muốn cho phép được ghi đè. Thông thường nó giúp chuyển đổi bên và cố gắng suy nghĩ: Nếu tôi đã sử dụng lớp đó và tạo một lớp con, tôi muốn làm gì để có thể ghi đè? Đôi khi những người dùng khác vẫn sẽ bỏ lỡ mọi thứ ...
Thorsten Dittmar

3
Các lĩnh vực được bảo vệ là thực hành xấu trong thừa kế! . Hãy cẩn thận với nó. Đây là lý do
RBT

4
Lĩnh vực tư nhân là xấu trong thực tế trong thừa kế!. (quá cường điệu) Xin vui lòng đọc câu trả lời của tôi.
Larry

6
Ý kiến ​​kiểm soát-freak điển hình.
bruno Desthuilliers

58

Hãy để tôi nói trước điều này bằng cách nói rằng tôi chủ yếu nói về việc truy cập phương thức ở đây và ở mức độ thấp hơn một chút, đánh dấu lớp cuối cùng, không phải là quyền truy cập của thành viên.

Trí tuệ cũ

"đánh dấu nó riêng tư trừ khi bạn có lý do chính đáng để không"

có ý nghĩa trong những ngày khi nó được viết, trước khi nguồn mở thống trị không gian thư viện nhà phát triển và VCS / phụ thuộc mgmt. trở nên siêu hợp tác nhờ Github, Maven, v.v. Trước đó, cũng có tiền để kiếm được bằng cách hạn chế (các) cách mà thư viện có thể được sử dụng. Có lẽ tôi đã dành 8 hoặc 9 năm đầu tiên trong sự nghiệp của mình tuân thủ nghiêm ngặt "thực hành tốt nhất" này.

Hôm nay, tôi tin rằng đó là lời khuyên tồi. Đôi khi, có một lý lẽ hợp lý để đánh dấu một phương thức riêng tư hoặc một lớp cuối cùng nhưng nó cực kỳ hiếm, và thậm chí sau đó có lẽ nó không cải thiện bất cứ điều gì.

Bạn có bao giờ:

  • Bị thất vọng, ngạc nhiên hoặc bị tổn thương bởi một thư viện, v.v. Tôi có.
  • Muốn sử dụng một thư viện cho trường hợp sử dụng hơi khác so với tưởng tượng của các tác giả nhưng không thể làm như vậy vì các phương thức và lớp riêng tư / cuối cùng? Tôi có.
  • Bạn có thất vọng, ngạc nhiên hay bị tổn thương bởi một thư viện, vv được cho phép quá mức trong khả năng mở rộng của nó? Tôi không có.

Đây là ba cách hợp lý hóa lớn nhất mà tôi đã nghe để đánh dấu các phương thức riêng tư theo mặc định:

Hợp lý hóa # 1: Không an toàn và không có lý do gì để ghi đè một phương thức cụ thể

Tôi không thể đếm số lần tôi đã sai về việc có cần phải ghi đè một phương pháp cụ thể mà tôi đã viết hay không. Đã làm việc trên một số lib nguồn mở phổ biến, tôi đã học được một cách khó khăn chi phí thực sự của việc đánh dấu những thứ riêng tư. Nó thường loại bỏ các giải pháp thực tế duy nhất để giải quyết các vấn đề hoặc các trường hợp sử dụng. Ngược lại, tôi chưa bao giờ trong hơn 16 năm phát triển chuyên nghiệp hối tiếc vì đã đánh dấu một phương pháp được bảo vệ thay vì riêng tư vì những lý do liên quan đến an toàn API. Khi một nhà phát triển chọn mở rộng một lớp và ghi đè một phương thức, họ có ý thức nói rằng "Tôi biết những gì tôi đang làm." và vì lợi ích của năng suất nên là đủ. giai đoạn = Stage. Nếu nó nguy hiểm, hãy lưu ý nó trong lớp / phương thức Javadocs, đừng chỉ đóng sập cửa một cách mù quáng.

Các phương pháp đánh dấu được bảo vệ theo mặc định là một giảm thiểu cho một trong những vấn đề chính trong phát triển SW hiện đại: thất bại của trí tưởng tượng.

Hợp lý hóa # 2: Nó giữ sạch API / Javadocs công khai

Điều này hợp lý hơn, và tùy thuộc vào đối tượng mục tiêu, nó thậm chí có thể là điều đúng đắn, nhưng đáng để xem xét chi phí của việc giữ API "sạch" thực sự là gì: khả năng mở rộng. Vì những lý do đã đề cập ở trên, có lẽ sẽ hợp lý hơn khi đánh dấu những thứ được bảo vệ theo mặc định chỉ trong trường hợp.

Hợp lý hóa # 3: Phần mềm của tôi là thương mại và tôi cần hạn chế sử dụng.

Điều này cũng hợp lý, nhưng với tư cách là một người tiêu dùng tôi sẽ đi cùng với đối thủ cạnh tranh ít hạn chế hơn (giả sử không tồn tại sự khác biệt đáng kể về chất lượng).

Không bao giờ nói không bao giờ

Tôi không nói không bao giờ đánh dấu phương pháp riêng tư. Tôi đang nói quy tắc tốt hơn là "làm cho các phương thức được bảo vệ trừ khi có lý do chính đáng để không".

Lời khuyên này phù hợp nhất cho những người làm việc trên các thư viện hoặc các dự án quy mô lớn hơn đã được chia thành các mô-đun. Đối với các dự án nguyên khối nhỏ hơn hoặc nhiều hơn, nó không có xu hướng quan trọng bằng việc bạn vẫn kiểm soát tất cả mã và dễ dàng thay đổi cấp độ truy cập của mã nếu / khi bạn cần. Mặc dù sau đó, tôi vẫn đưa ra lời khuyên tương tự :-)


Wrt your Rationalization # 1- Khi tôi đánh dấu thứ gì đó được bảo vệ, tôi chọn thực hiện điều đó một cách rõ ràng vì tôi cảm thấy rằng hành vi này không phải là cuối cùng và có thể là trường hợp các lớp con tôi muốn ghi đè lên nó. Nếu tôi không chắc chắn thì tôi muốn đặt nó ở chế độ riêng tư theo mặc định. Đặc biệt trong một ngôn ngữ như C # giữ một phương thức không ảo theo mặc định, việc thêm chỉ định truy cập được bảo vệ sẽ không có ý nghĩa. Bạn phải thêm cả hai virtualprotectedtừ khóa để làm rõ ý định của bạn. Ngoài ra, mọi người thích liên kết hơn thừa kế nên protectedmặc định rất khó nhận biết
RBT

4
Sự kết hợp của các từ khóa cần thiết để làm cho một phương thức có thể ghi đè là một chi tiết ngôn ngữ IMHO. Đối số phản biện mà tôi trình bày để hợp lý hóa # 1 hoàn toàn nhắm vào trường hợp bạn đề cập "Nếu tôi không chắc chắn như vậy ...". Thực tế mà nói, nếu bạn không thể nghĩ ra lý do tại sao nó sẽ nguy hiểm thì sẽ có nhiều hơn bằng cách chọn mở rộng. Khi bạn nói 'hiệp hội', tôi sẽ coi đó là một từ đồng nghĩa với sáng tác, trong trường hợp đó tôi cũng không thấy nó quan trọng.
Nick

3
Tôi không nhớ mình đã phải "nhân bản" bao nhiêu lần các lớp bên dưới chỉ vì tôi muốn ghi đè lên 1 hoặc 2 hoặc các phương thức. Và giống như những gì bạn đã nói, với xu hướng xã hội mà chúng ta đang chia sẻ nhiều mã hơn bao giờ hết, việc mặc định được bảo vệ sẽ tốt hơn nhiều so với riêng tư. tức là cởi mở hơn với logic của riêng bạn.
shinkou

1
Đồng ý. Tôi chỉ muốn điều chỉnh BufferedReader của Java để xử lý các dấu phân cách dòng tùy chỉnh. Nhờ các trường riêng mà tôi phải sao chép toàn bộ lớp thay vì chỉ mở rộng nó và ghi đè readLine ().
Ron Dunn

21

Ngừng lạm dụng các lĩnh vực tư nhân !!!

Các ý kiến ​​ở đây dường như rất ủng hộ việc sử dụng các lĩnh vực tư nhân. Vâng, sau đó tôi có một cái gì đó khác nhau để nói.

Các lĩnh vực tư nhân là tốt về nguyên tắc? Đúng. Nhưng nói rằng một quy tắc vàng là làm cho mọi thứ riêng tư khi bạn không chắc chắn là sai ! Bạn sẽ không thấy vấn đề cho đến khi bạn gặp phải một vấn đề. Theo tôi, bạn nên đánh dấu các trường là được bảo vệ nếu bạn không chắc chắn .

Có hai trường hợp bạn muốn mở rộng một lớp:

  • Bạn muốn thêm chức năng bổ sung cho một lớp cơ sở
  • Bạn muốn sửa đổi lớp hiện có bên ngoài gói hiện tại (có thể trong một số thư viện)

Không có gì sai với các lĩnh vực tư nhân trong trường hợp đầu tiên. Việc mọi người lạm dụng các lĩnh vực riêng tư khiến bạn rất bực mình khi phát hiện ra mình không thể sửa đổi cứt.

Hãy xem xét một thư viện đơn giản mô hình xe ô tô:

class Car {
    private screw;
    public assembleCar() {
       screw.install();
    };
    private putScrewsTogether() {
       ...
    };
}

Tác giả thư viện nghĩ: không có lý do gì người dùng thư viện của tôi cần truy cập vào chi tiết triển khai assembleCar()đúng không? Hãy đánh dấu vít là riêng tư.

Vâng, tác giả đã sai. Nếu bạn chỉ muốn sửa đổi assembleCar()phương thức mà không sao chép cả lớp vào gói của mình, bạn đã hết may mắn. Bạn phải viết lại screwlĩnh vực của riêng bạn . Giả sử chiếc xe này sử dụng hàng tá ốc vít và mỗi trong số chúng liên quan đến một số mã khởi tạo không cần thiết trong các phương thức riêng tư khác nhau và các ốc vít này đều được đánh dấu riêng tư. Lúc này, nó bắt đầu mút.

Vâng, bạn có thể tranh luận với tôi rằng tác giả thư viện cũng có thể viết mã tốt hơn nên không có gì sai với các trường riêng . Tôi không tranh luận rằng lĩnh vực riêng tư là một vấn đề với OOP . Đó là một vấn đề khi mọi người đang sử dụng chúng.

Đạo đức của câu chuyện là, nếu bạn đang viết thư viện, bạn sẽ không bao giờ biết liệu người dùng của bạn có muốn truy cập vào một lĩnh vực cụ thể hay không. Nếu bạn không chắc chắn, hãy đánh dấu nó protectedđể mọi người sẽ hạnh phúc hơn sau này. Ít nhất đừng lạm dụng lĩnh vực tư nhân .

Tôi rất ủng hộ câu trả lời của Nick.


2
Sử dụng các trường riêng chủ yếu nói rằng thừa kế là sự trừu tượng sai để thay đổi một hành vi nhất định của một lớp / đối tượng (nếu được sử dụng một cách có ý thức). Việc thay đổi thường sẽ âm thầm vi phạm LSP khi hành vi được thay đổi mà không thay đổi API. Câu trả lời tuyệt vời, +1.
Madara's Ghost

Thuyết giáo! Riêng tư là nguyên nhân tồn tại của tôi khi cố gắng viết mod Minecraft. Không có gì khó chịu hơn việc phải sử dụng Reflection hoặc ASM để khắc phục điều đó.
RecursiveExceptionException

Khi tôi hiểu câu trả lời của Nick, anh ấy chủ yếu đề cập đến các phương thức không phải là thuộc tính. Tôi hoàn toàn đồng ý rằng chúng ta không nên khai báo các phương thức riêng tư mỗi lần và việc sử dụng các nhu cầu đó phải chu đáo, nhưng cmon tại sao bạn lại khuyên bạn nên khai báo các thuộc tính được bảo vệ riêng tư chỉ vì bạn muốn "viết lại" lớp dễ dàng hơn. Hoàn toàn chia sẻ sự thất vọng của bạn khi tôi làm việc với người thứ 3 hàng ngày, nhưng hãy khuyến khích mọi người tạo ra kiến ​​trúc vững chắc và không chỉ nói rằng "mã cuối cùng sẽ trở nên tào lao nên chúng ta hãy bảo vệ ngay từ đầu để chúng ta có thể dễ dàng viết lại". Mặc dù tôi chia sẻ sự thất vọng của bạn.
sean662

16

Tôi đã đọc một bài báo trước đây nói về việc khóa tất cả các lớp càng nhiều càng tốt. Làm cho mọi thứ cuối cùng và riêng tư trừ khi bạn có nhu cầu ngay lập tức để lộ một số dữ liệu hoặc chức năng ra thế giới bên ngoài. Luôn luôn dễ dàng mở rộng phạm vi để được cho phép hơn về sau, nhưng không phải là cách khác. Đầu tiên hãy xem xét thực hiện càng nhiều thứ càng tốt finalsẽ giúp lựa chọn giữa privateprotecteddễ dàng hơn nhiều.

  1. Làm cho tất cả các lớp cuối cùng trừ khi bạn cần phân lớp chúng ngay lập tức.
  2. Làm cho tất cả các phương thức cuối cùng trừ khi bạn cần phân lớp và ghi đè chúng ngay lập tức.
  3. Làm cho tất cả các tham số của phương thức là cuối cùng trừ khi bạn cần thay đổi chúng trong phần thân của phương thức, điều này hơi khó xử trong hầu hết các trường hợp.

Bây giờ nếu bạn còn lại một lớp cuối cùng, thì hãy biến mọi thứ thành riêng tư trừ khi thế giới thực sự cần thiết - hãy công khai điều đó.

Nếu bạn còn lại một lớp có (các) lớp con, thì hãy kiểm tra cẩn thận mọi thuộc tính và phương thức. Đầu tiên hãy xem xét nếu bạn thậm chí muốn đưa thuộc tính / phương thức đó đến các lớp con. Nếu bạn làm như vậy, thì hãy xem xét liệu một lớp con có thể phá hoại đối tượng của bạn hay không nếu nó làm rối giá trị thuộc tính hoặc thực hiện phương thức trong quá trình ghi đè. Nếu có thể, và bạn muốn bảo vệ thuộc tính / phương thức của lớp ngay cả khỏi các lớp con (nghe có vẻ mỉa mai, tôi biết), thì hãy đặt nó ở chế độ riêng tư. Nếu không làm cho nó được bảo vệ.

Tuyên bố miễn trừ trách nhiệm: Tôi không lập trình nhiều trong Java :)


2
Để làm rõ: A final classtrong Java là một lớp không thể được kế thừa từ đó. A final methodlà không ảo, tức là không bị chồng chéo bởi một lớp con (các phương thức Java là ảo theo mặc định). A final field/parameter/variablelà một tham chiếu biến không thay đổi (theo nghĩa là nó không thể được sửa đổi sau khi được khởi tạo).
M.Stramm

1
Thật thú vị, tôi đã thấy một lời khuyên hoàn toàn ngược lại, rằng không có gì là cuối cùng trừ khi chắc chắn rằng việc ghi đè lên câu hỏi có khả năng phá vỡ một bất biến. Bằng cách đó, bất cứ ai cũng có thể tự do mở rộng các lớp học của bạn khi cần thiết.
GordonM

Bạn có thể đặt tên cho một trường hợp trong sự nghiệp lập trình của bạn trong đó việc đánh dấu tham số phương thức "cuối cùng" đã tạo ra sự khác biệt cho sự hiểu biết của bạn không? (ngoài yêu cầu của trình biên dịch)
user949300

7

Khi nào bạn sẽ sử dụng privatevà khi nào bạn sẽ sử dụng protected?

Kế thừa tư nhân có thể được nghĩ đến Được thực hiện trên phương diện mối quan hệ chứ không phải mối quan hệ IS-A . Nói một cách đơn giản, giao diện bên ngoài của lớp kế thừa không có mối quan hệ (hiển thị) với lớp kế thừa, Nó chỉ sử dụng privatekế thừa để thực hiện một chức năng tương tự mà lớp Base cung cấp.

Không giống như Kế thừa tư nhân, thừa kế được bảo vệ là một hình thức thừa kế bị hạn chế, trong đó lớp IS-A thuộc lớp cơ sở và nó muốn hạn chế quyền truy cập của các thành viên dẫn xuất chỉ vào lớp dẫn xuất.


2
Rằng "Thực hiện theo quan hệ" là mối quan hệ HAS-A. Nếu tất cả các thuộc tính là riêng tư, cách duy nhất để truy cập chúng là các phương thức thông qua. Đây là điều tương tự như khi lớp con chứa một đối tượng của siêu hạng. Loại bỏ tất cả các thuộc tính được bảo vệ khỏi các lớp cụ thể của bạn có nghĩa là cũng loại bỏ tất cả các kế thừa từ chúng.
Shawnhcorey

2
Quá trình suy nghĩ thú vị - Kế thừa tư nhân . @shawnhcorey nhờ để làm cho nó rõ ràng rằng nó được chỉ tay về phía HAS-A mối quan hệ aka hiệp hội (có thể kết hợp hoặc thành phần). Tôi cảm thấy bài viết này cũng nên được cập nhật để làm rõ tương tự.
RBT

1

Vâng, đó là tất cả về đóng gói nếu các lớp thanh toán xử lý thanh toán thanh toán sau đó trong lớp sản phẩm tại sao nó cần toàn bộ quá trình thanh toán, ví dụ như phương thức thanh toán làm thế nào để thanh toán ở đâu .. vì vậy chỉ để những gì được sử dụng cho các lớp và đối tượng khác không có gì nhiều hơn công khai cho những nơi mà các lớp khác cũng sẽ sử dụng, được bảo vệ cho những giới hạn đó chỉ dành cho việc mở rộng các lớp. Như bạn là madara uchiha, tư nhân giống như "limboo"bạn có thể thấy nó (bạn chỉ học lớp duy nhất).

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.