Tại sao Java làm cho truy cập gói mặc định?


26

Tôi đang hỏi câu hỏi này bởi vì tôi tin rằng họ đã làm nó vì một lý do rất tốt và hầu hết mọi người không sử dụng nó đúng cách, từ kinh nghiệm của tôi trong ngành cho đến nay. Nhưng nếu lý thuyết của tôi là đúng thì tôi không chắc tại sao họ lại đưa vào công cụ sửa đổi truy cập riêng tư ...?

Tôi tin rằng nếu truy cập mặc định được sử dụng đúng cách, nó cung cấp khả năng kiểm tra nâng cao trong khi duy trì đóng gói. Và nó cũng làm cho dự phòng sửa đổi truy cập riêng tư dự phòng.

Công cụ sửa đổi truy cập mặc định có thể được sử dụng để cung cấp ảnh hưởng tương tự bằng cách sử dụng gói duy nhất cho các phương thức cần ẩn khỏi phần còn lại của thế giới và nó thực hiện điều này mà không ảnh hưởng đến khả năng kiểm tra, như các gói trong thư mục kiểm tra, giống như vậy có thể truy cập tất cả các phương thức mặc định được khai báo trong một thư mục nguồn.

Tôi tin rằng đây là lý do tại sao Java sử dụng truy cập gói làm 'mặc định'. Nhưng tôi không chắc tại sao họ cũng bao gồm quyền truy cập riêng tư, tôi chắc chắn có trường hợp sử dụng hợp lệ ...



Có liên quan về các phương pháp thử nghiệm đơn vị đã thảo luận trước đó; how-do-you-unit-test-private-phương thức . Câu trả lời; bạn không nên
Richard Tingle

Câu trả lời:


25

Tôi đoán họ đã có một ý tưởng tốt về những gì một lập trình viên trung bình sẽ làm. Và bởi một lập trình viên trung bình, ý tôi là một người không thực sự giỏi về lập trình, nhưng dù sao cũng có được công việc vì không có đủ người giỏi và họ rất đắt.

Nếu họ đặt "công khai" quyền truy cập mặc định, hầu hết các lập trình viên sẽ không bao giờ bận tâm sử dụng bất cứ thứ gì khác. Kết quả sẽ là rất nhiều mã spaghetti ở khắp mọi nơi. (Bởi vì nếu về mặt kỹ thuật bạn được phép gọi bất cứ thứ gì từ bất cứ đâu, tại sao lại bận tâm đóng gói một số logic trong một phương thức?)

Làm cho "riêng tư" truy cập mặc định sẽ chỉ tốt hơn một chút. Hầu hết các lập trình viên mới bắt đầu chỉ đơn giản là phát minh (và chia sẻ trên blog của họ) một quy tắc "bạn cần phải viết" công khai "ở mọi nơi", và sau đó họ sẽ phàn nàn tại sao Java lại tệ đến mức buộc họ phải viết "công khai" ở mọi nơi. Và họ cũng sẽ sản xuất rất nhiều mã spaghetti.

Quyền truy cập gói là thứ cho phép các lập trình viên sử dụng các kỹ thuật lập trình cẩu thả của họ trong khi viết mã trong một gói, nhưng sau đó họ phải xem xét lại khi thực hiện nhiều gói hơn. Đó là một sự thỏa hiệp giữa thực tiễn kinh doanh tốt và thực tế xấu xí. Giống như: viết một số mã spaghetti, nếu bạn khăng khăng, nhưng xin vui lòng để lại mớ hỗn độn xấu xí trong gói; ít nhất là tạo ra một số giao diện đẹp hơn trong số các gói.

Có lẽ cũng có những lý do khác, nhưng tôi sẽ không đánh giá thấp lý do này.


14

Truy cập mặc định không làm cho bộ điều chỉnh truy cập riêng dự phòng.

Vị trí của các nhà thiết kế ngôn ngữ được phản ánh trong hướng dẫn chính thức - Kiểm soát quyền truy cập vào các thành viên của một lớp và nó khá rõ ràng (để thuận tiện cho bạn, tuyên bố có liên quan trong trích dẫn được in đậm ):

Mẹo về cách chọn cấp độ truy cập:

Nếu các lập trình viên khác sử dụng lớp của bạn, bạn muốn đảm bảo rằng lỗi từ việc sử dụng sai có thể xảy ra. Cấp độ truy cập có thể giúp bạn làm điều này.

  • Sử dụng cấp độ truy cập hạn chế nhất có ý nghĩa đối với một thành viên cụ thể. Sử dụng riêng tư trừ khi bạn có một lý do tốt để không.
  • Tránh các lĩnh vực công cộng trừ các hằng số. (Nhiều ví dụ trong hướng dẫn sử dụng các trường công khai. Điều này có thể giúp minh họa một số điểm chính xác, nhưng không được khuyến nghị cho mã sản xuất.) Các trường công khai có xu hướng liên kết bạn với một triển khai cụ thể và hạn chế tính linh hoạt của bạn trong việc thay đổi mã.

Sự hấp dẫn của bạn đối với khả năng kiểm tra vì sự biện minh cho việc bỏ hoàn toàn công cụ sửa đổi riêng tư là sai, bằng chứng là các câu trả lời trong Mới đối với TDD. Tôi có nên tránh các phương pháp riêng tư bây giờ?

Tất nhiên bạn có thể có các phương thức riêng tư, và tất nhiên bạn có thể kiểm tra chúng.

một số cách để chạy phương thức riêng tư, trong trường hợp đó bạn có thể kiểm tra theo cách đó hoặc không có cách nào để chạy riêng tư, trong trường hợp đó: tại sao bạn lại cố gắng kiểm tra nó, chỉ là xóa cái thứ chết tiệt đó đi ...


Vị trí của các nhà thiết kế ngôn ngữ về mục đích và cách sử dụng truy cập cấp gói được giải thích trong một hướng dẫn chính thức khác, Tạo và sử dụng các gói và nó không có gì chung với ý tưởng bỏ các sửa đổi riêng tư (để thuận tiện cho bạn, tuyên bố có liên quan trong trích dẫn được in đậm ) :

Bạn nên gói các lớp này và giao diện trong một gói vì nhiều lý do, bao gồm:

  • Bạn và các lập trình viên khác có thể dễ dàng xác định rằng các loại này có liên quan ...
  • Bạn có thể cho phép các loại trong gói có quyền truy cập không hạn chế đối với nhau nhưng vẫn hạn chế quyền truy cập đối với các loại bên ngoài gói ...

<rant "Tôi nghĩ rằng tôi đã nghe đủ tiếng rên rỉ. Đoán đến lúc phải nói to và rõ ràng ...">

Phương pháp riêng có lợi cho thử nghiệm đơn vị.

Lưu ý dưới đây giả định rằng bạn quen thuộc với phạm vi bảo hiểm mã . Nếu không, hãy dành thời gian để tìm hiểu, vì nó khá hữu ích cho những người quan tâm đến thử nghiệm đơn vị và thử nghiệm.

Được rồi, vì vậy tôi đã có các thử nghiệm đơn vị và phương pháp riêng tư và phân tích bảo hiểm cho tôi biết rằng có một khoảng cách, phương pháp riêng tư của tôi không được bao phủ bởi các thử nghiệm. Hiện nay...

Tôi có được gì khi giữ kín

Vì phương thức là riêng tư, cách duy nhất để tiến hành là nghiên cứu mã để tìm hiểu cách sử dụng nó thông qua API không riêng tư. Thông thường, một nghiên cứu như vậy cho thấy lý do của khoảng cách là kịch bản sử dụng cụ thể bị thiếu trong các thử nghiệm.

    void nonPrivateMethod(boolean condition) {
        if (condition) {
            privateMethod();
        }
        // other code...
    }

    // unit tests don't invoke nonPrivateMethod(true)
    //   => privateMethod isn't covered.

Để hoàn thiện, các lý do khác (ít thường xuyên hơn) cho các khoảng trống bảo hiểm như vậy có thể là lỗi trong đặc tả / thiết kế. Tôi sẽ không đi sâu vào những điều này ở đây, để giữ cho mọi thứ đơn giản; đủ để nói rằng nếu bạn làm suy yếu giới hạn truy cập "chỉ để làm cho phương pháp có thể kiểm tra được", bạn sẽ bỏ lỡ cơ hội để biết rằng những lỗi này tồn tại.

Tốt thôi, để khắc phục khoảng trống, tôi thêm một bài kiểm tra đơn vị cho kịch bản bị thiếu, lặp lại phân tích bảo hiểm và xác minh rằng khoảng trống đã biến mất. Bây giờ tôi có gì? Tôi đã có bài kiểm tra đơn vị mới để sử dụng cụ thể API không riêng tư.

  1. Thử nghiệm mới đảm bảo rằng hành vi dự kiến ​​cho việc sử dụng này sẽ không thay đổi mà không có thông báo vì nếu thay đổi, thử nghiệm sẽ thất bại.

  2. Một người đọc bên ngoài có thể xem xét bài kiểm tra này và tìm hiểu cách sử dụng và ứng xử (ở đây, người đọc bên ngoài bao gồm cả bản thân tương lai của tôi, vì tôi có xu hướng quên mã một hoặc hai tháng sau khi tôi hoàn thành nó).

  3. Thử nghiệm mới có thể chấp nhận tái cấu trúc (tôi có cấu trúc lại các phương thức riêng tư không? Bạn đặt cược!) Dù tôi làm gì privateMethod, tôi sẽ luôn muốn thử nghiệm nonPrivateMethod(true). Bất kể tôi làm gì privateMethod, sẽ không cần sửa đổi kiểm tra vì phương thức không được gọi trực tiếp.

Không tệ? Bạn đặt cược.

Tôi mất gì khi làm suy yếu giới hạn truy cập

Bây giờ hãy tưởng tượng rằng thay vì ở trên, tôi chỉ đơn giản là làm suy yếu giới hạn truy cập. Tôi bỏ qua việc nghiên cứu mã sử dụng phương thức và tiến hành kiểm tra bằng cách gọi ra exPrivateMethod. Tuyệt quá? Không phải!

  1. Tôi có đạt được một thử nghiệm cho việc sử dụng cụ thể API không riêng tư được đề cập ở trên không? Không: trước đây không có bài kiểm tra nào nonPrivateMethod(true)và hiện tại không có bài kiểm tra nào như vậy.

  2. Các độc giả bên ngoài có cơ hội hiểu rõ hơn về việc sử dụng lớp học không? Không. "- Này, mục đích của phương pháp được thử nghiệm ở đây là gì? - Quên đi, nó hoàn toàn được sử dụng nội bộ. - Rất tiếc."

  3. Có chịu được tái cấu trúc không? Không có cách nào: bất cứ điều gì tôi thay đổi exPrivateMethod, có thể sẽ phá vỡ bài kiểm tra. Đổi tên, hợp nhất vào một số phương thức khác, thay đổi đối số và kiểm tra sẽ chỉ dừng biên dịch. Đau đầu? Bạn đặt cược!

Tóm tắt , gắn bó với phương pháp riêng tư mang đến cho tôi một sự cải tiến hữu ích, đáng tin cậy trong các bài kiểm tra đơn vị. Ngược lại, việc làm suy yếu các giới hạn truy cập "về khả năng kiểm tra" chỉ mang lại cho tôi một đoạn mã kiểm tra khó hiểu, khó hiểu, có nguy cơ bị phá vỡ vĩnh viễn bởi bất kỳ cấu trúc lại nhỏ nào; thẳng thắn những gì tôi nhận được trông đáng ngờ như nợ kỹ thuật .

</ rant>


7
Tôi chưa bao giờ thấy lập luận đó để thử nghiệm các phương pháp riêng hấp dẫn. Bạn luôn cấu trúc lại mã thành các phương thức vì những lý do không liên quan đến API công khai của một lớp và đó là một điều rất hay để có thể kiểm tra các phương thức đó một cách độc lập mà không liên quan đến bất kỳ mã nào khác. Đó là lý do bạn tái cấu trúc, phải không? Để có được một phần của chức năng độc lập. Chức năng đó cũng có thể được kiểm tra độc lập.
Robert Harvey

Câu trả lời @RobertHarvey được mở rộng với một câu thần chú giải quyết vấn đề này. "Các phương pháp riêng tư có lợi cho thử nghiệm đơn vị ..."
gnat

Tôi thấy những gì bạn đang nói về các phương thức riêng tư và phạm vi bảo hiểm mã, nhưng tôi không thực sự ủng hộ việc công khai các phương thức đó để bạn có thể kiểm tra chúng. Tôi đã ủng hộ việc có một cách để kiểm tra chúng một cách độc lập mặc dù chúng là riêng tư. Bạn vẫn có thể có các bài kiểm tra công khai chạm vào phương thức riêng tư, nếu bạn muốn. Và tôi có thể có bài kiểm tra riêng của mình.
Robert Harvey

@RobertHarvey Tôi thấy. Đoán nó sôi theo phong cách mã hóa. Với số lượng phương pháp riêng tôi thường sản xuất, và với tốc độ tôi tái cấu trúc những phương pháp này, việc thử nghiệm chúng chỉ đơn giản là một sự xa xỉ mà tôi không thể có được. API không riêng tư là một vấn đề khác, tôi có xu hướng khá miễn cưỡng và chậm chạp trong việc sửa đổi nó
gnat

Có lẽ bạn giỏi viết các phương pháp làm việc lần đầu tiên hơn tôi. Đối với tôi, tôi cần thử nghiệm phương pháp để đảm bảo rằng nó hoạt động trước khi tôi có thể tiếp tục và phải kiểm tra nó một cách gián tiếp bằng cách bắn ra một loạt các phương pháp khác sẽ vụng về và lúng túng.
Robert Harvey

9

Lý do thích hợp nhất là: nó phải liên quan đến lịch sử. Tổ tiên của Java, Oak, chỉ có ba cấp độ truy cập: riêng tư, được bảo vệ, công khai.

Ngoại trừ việc riêng tư trong Oak là tương đương với gói riêng tư trong Java. Bạn có thể đọc phần 4.10 của Đặc tả ngôn ngữ của Oak (nhấn mạnh của tôi):

Theo mặc định, tất cả các biến và phương thức trong một lớp (bao gồm các hàm tạo) là riêng tư. Các biến và phương thức riêng tư chỉ có thể được truy cập bằng các phương thức được khai báo trong lớp chứ không phải bởi các lớp con của nó hoặc bất kỳ lớp nào khác ( ngoại trừ các lớp trong cùng một gói ).

Vì vậy, theo quan điểm của bạn, quyền truy cập riêng tư, như được biết trong Java, ban đầu không có ở đó. Nhưng khi bạn có nhiều hơn một vài lớp trong một gói, không có riêng tư như chúng ta biết bây giờ sẽ dẫn đến một cơn ác mộng về sự va chạm tên (ví dụ: java.until.concản có gần 60 lớp), đó có thể là lý do tại sao chúng đã giới thiệu nó.

Tuy nhiên, ngữ nghĩa truy cập gói mặc định (ban đầu được gọi là riêng tư) không bị thay đổi giữa Oak và Java.


5

Tôi tin rằng nếu truy cập mặc định được sử dụng đúng cách, nó cung cấp khả năng kiểm tra nâng cao trong khi duy trì đóng gói. Và nó cũng làm cho dự phòng sửa đổi truy cập riêng tư dự phòng.

Có những tình huống người ta muốn hai lớp riêng biệt bị ràng buộc chặt chẽ hơn là phơi bày mọi thứ với công chúng. Điều này có ý tưởng tương tự về 'bạn bè' trong C ++, trong đó một lớp khác được tuyên bố là 'bạn' của người khác có thể truy cập các thành viên riêng của họ.

Nếu bạn nhìn vào phần bên trong của các lớp như BigInteger, bạn sẽ tìm thấy một số gói bảo vệ mặc định trên các trường và phương thức (mọi thứ đều là hình tam giác màu xanh trong danh sách). Điều này cho phép các lớp khác trong gói java.math để có quyền truy cập tối ưu hơn để nội tạng của họ cho các hoạt động cụ thể (bạn có thể tìm thấy những phương pháp gọi trong BigDecimal - BigDecimal được hỗ trợ bởi một BigInteger và tiết kiệm reimplementing BigInteger một lần nữa . Đó là những gói mức isn' Đây là một vấn đề cần bảo vệ vì java.math là một gói kín và không có lớp nào khác có thể được thêm vào nó.

Mặt khác, có rất nhiều điều thực sự riêng tư, như chúng nên có. Hầu hết các gói không niêm phong. Không có riêng tư, đồng nghiệp của bạn có thể đặt một lớp khác trong gói đó và truy cập vào trường riêng (không còn) và ngắt đóng gói.

Đáng chú ý, thử nghiệm đơn vị không phải là thứ được nghĩ đến khi các phạm vi bảo vệ được xây dựng. JUnit (khung thử nghiệm XUnit cho Java) không được hình thành cho đến năm 1997 (một câu chuyện kể về câu chuyện có thể được đọc tại http://www.martinfowler.com/bliki/Xunit.html ). Các phiên bản Alpha và Beta của JDK là vào năm 1995 và JDK 1.0 là vào năm 1996, mặc dù các mức bảo vệ không thực sự bị đóng đinh cho đến khi JDK 1.0.2 (trước đó bạn có thể có private protectedmức bảo vệ).

Một số người sẽ cho rằng mặc định không nên là cấp gói, nhưng riêng tư. Những người khác cho rằng không nên có một sự bảo vệ mặc định nhưng mọi thứ nên được nêu rõ ràng - một số người theo dõi điều này sẽ viết mã như:

public class Foo {
    /* package */ int bar = 42;
    ...
}

Lưu ý các bình luận ở đó.

Lý do thực sự tại sao bảo vệ mức gói là mặc định có thể bị mất đối với các ghi chú thiết kế của Java (Tôi đã đào và không thể tìm thấy lý do tại sao các bài viết, rất nhiều người giải thích sự khác biệt, nhưng không ai nói "đây là lý do ").

Vì vậy, tôi sẽ đoán. Và đó là tất cả - đó là một phỏng đoán.

Trước hết, mọi người vẫn đang cố gắng tìm ra thiết kế ngôn ngữ. Các ngôn ngữ từ đó đã học được từ những sai lầm và thành công của Java. Điều này có thể được liệt kê là một lỗi không có một cái gì đó cần được xác định cho tất cả các lĩnh vực.

  • Các nhà thiết kế đã thấy một nhu cầu không thường xuyên cho mối quan hệ 'bạn bè' của C ++.
  • Các nhà thiết kế muốn tuyên bố rõ ràng cho public, và privatevì những điều này có tác động lớn nhất đến quyền truy cập.

Do đó, private không mặc định và tốt, mọi thứ khác rơi vào vị trí. Phạm vi mặc định được để lại như mặc định. Nó có thể là phạm vi đầu tiên được tạo ra và vì lợi ích của khả năng tương thích ngược nghiêm ngặt với mã cũ hơn, đã bị bỏ lại như vậy.

Như tôi đã nói, tất cả chỉ là phỏng đoán .


À, nếu chỉ có tính năng kết bạn có thể được áp dụng cho các thành viên trên cơ sở tùy ý, vì vậy bạn có thể nói void someFactor () chỉ có thể được nhìn thấy bởi lớp X ... điều đó thực sự thắt chặt việc đóng gói!
newlogic

Đợi đã, bạn có thể làm điều này trong C ++, chúng tôi cần điều này trong Java!
newlogic

2
@ user1037729 nó làm cho sự kết hợp chặt chẽ hơn giữa hai lớp - lớp với phương thức bây giờ cần biết về các lớp khác là bạn của nó. Điều này có xu hướng đi ngược lại triết lý thiết kế mà Java cung cấp. Tập hợp các vấn đề đó là có thể giải quyết với bạn bè nhưng không đóng gói bảo vệ cấp độ là không lớn.

2

Đóng gói là một cách nói "bạn không cần phải suy nghĩ về điều này".

                 Access Levels
Modifier    Class   Package  Subclass   World
public        Y        Y        Y        Y
protected     Y        Y        Y        N
no modifier   Y        Y        N        N
private       Y        N        N        N

Đặt những điều này vào các thuật ngữ phi kỹ thuật.

  1. Công khai: mọi người phải suy nghĩ về nó.
  2. Được bảo vệ: mặc định và các lớp con phải biết về nó.
  3. Mặc định, nếu bạn đang làm việc trên bao bì, bạn sẽ biết về nó (nó ở ngay trước mắt bạn) cũng có thể cho phép bạn ủng hộ nó.
  4. Riêng tư, bạn chỉ cần biết về điều này nếu bạn đang làm việc trên lớp này.

Tôi không nghĩ có những thứ như lớp tư nhân hay lớp được bảo vệ ..
Koray Tugay

@KorayTugay: nhìn vào docs.oracle.com/javase/tutorial/java/javaOO/innercurine.html . Các lớp bên trong là riêng tư. Đọc đoạn cuối.
jmoreno

0

Tôi không nghĩ rằng họ tập trung vào thử nghiệm, đơn giản là vì thử nghiệm trước đây không phổ biến lắm.

Những gì tôi nghĩ họ muốn đạt được là đóng gói ở cấp độ gói. Một lớp có thể như bạn biết có các phương thức và trường nội bộ và chỉ hiển thị một số phần đó thông qua các thành viên công cộng. Theo cùng một cách, một gói có thể có các lớp, phương thức và trường bên trong và chỉ hiển thị một số phần mềm. Nếu bạn nghĩ theo cách này, một gói có triển khai nội bộ trong một tập hợp các lớp, phương thức và trường, cùng với giao diện chung trong một tập hợp các lớp, phương thức và trường khác (có thể chồng chéo một phần).

Các lập trình viên giàu kinh nghiệm nhất mà tôi biết nghĩ theo cách này. Họ đóng gói và áp dụng nó ở mức độ trừu tượng cao hơn lớp: một gói hoặc một thành phần nếu bạn muốn. Có vẻ hợp lý với tôi rằng các nhà thiết kế của Java đã trưởng thành trong thiết kế của họ, xem xét Java vẫn giữ vững như thế nào.


Bạn đúng rằng kiểm tra tự động không phổ biến ở đó. Nhưng đó là một thiết kế chưa trưởng thành.
Tom Hawtin - tackline
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.