Trong Java, tại sao các thành viên được bảo vệ có thể truy cập được vào các lớp của cùng một gói?


14

Từ tài liệu chính thức ...

Thế giới lớp gói sửa đổi 
công khai 
bảo vệ YYYN 
không sửa đổi YYNN 
tư nhân 

Vấn đề là, tôi không thể nhớ có trường hợp sử dụng khi tôi cần truy cập các thành viên được bảo vệ từ một lớp trong cùng một gói.

Những lý do đằng sau việc thực hiện này là gì?

Chỉnh sửa: Để làm rõ, tôi đang tìm kiếm một trường hợp sử dụng cụ thể trong đó cả lớp con và lớp trong cùng một gói cần truy cập vào một trường hoặc phương thức được bảo vệ.

package some.package;
public class A {
 protected void protectedMethod(){
  // do something
 }
}

package another.package;
public class B extends A{
 public void someMethod(){
  // accessible because B is a subclass of A
  protectedMethod();
 }
} 

package some.package;
public class C {
 public void anotherMethod(){
  // accessible because C is in the same package as A
  protectedMehtod();
 }
}

Câu trả lời:


4

tìm kiếm một trường hợp sử dụng cụ thể trong đó cả lớp con và lớp trong cùng một gói cần truy cập vào trường hoặc phương thức được bảo vệ ...

Đối với tôi, trường hợp sử dụng như vậy khá chung chung và cụ thể bắt nguồn từ sở thích của tôi để:

  1. Bắt đầu với công cụ sửa đổi truy cập chặt chẽ nhất có thể, chỉ dùng đến một (các) yếu hơn sau khi thấy cần thiết.
  2. Có các bài kiểm tra đơn vị nằm trong cùng gói với mã được kiểm tra.

Từ trên, tôi có thể bắt đầu thiết kế cho các đối tượng của mình bằng các công cụ sửa đổi truy cập mặc định (tôi sẽ bắt đầu với privatenhưng điều đó sẽ làm phức tạp việc kiểm tra đơn vị):

public class Example {
    public static void main(String [] args) {
        new UnitTest().testDoSomething(new Unit1(), new Unit2());
    }

    static class Unit1 {
        void doSomething() {} // default access
    }
    static class Unit2 {
        void doSomething() {} // default access
    }

    static class UnitTest {
        void testDoSomething(Unit1 unit1, Unit2 unit2) {
            unit1.doSomething();
            unit2.doSomething();
        }
    }
}

Lưu ý bên trong đoạn mã trên, Unit1, Unit2UnitTestđược lồng vào trong Examplevì đơn giản của bài trình bày, nhưng trong một dự án thực tế, tôi có khả năng sẽ có những lớp trong các file riêng biệt (và UnitTestthậm chí trong một thư mục riêng biệt ).

Sau đó, khi có nhu cầu phát sinh, tôi sẽ làm suy yếu quyền kiểm soát truy cập từ mặc định thành protected:

public class ExampleEvolved {
    public static void main(String [] args) {
        new UnitTest().testDoSomething(new Unit1(), new Unit2());
    }

    static class Unit1 {
        protected void doSomething() {} // made protected
    }
    static class Unit2 {
        protected void doSomething() {} // made protected
    }

    static class UnitTest {
        // ---> no changes needed although UnitTest doesn't subclass
        // ...and, hey, if I'd have to subclass... which one of Unit1, Unit2?
        void testDoSomething(Unit1 unit1, Unit2 unit2) {
            unit1.doSomething();
            unit2.doSomething();
        }
    }
}

Bạn thấy đấy, tôi có thể giữ mã kiểm tra đơn vị ExampleEvolvedkhông thay đổi do các phương thức được bảo vệ có thể truy cập được từ cùng một gói, mặc dù đối tượng truy cập không phải là một lớp con .

Ít thay đổi cần thiết => sửa đổi an toàn hơn; sau tất cả, tôi đã thay đổi chỉ các công cụ sửa đổi truy cập và tôi đã không sửa đổi phương thức nào Unit1.doSomething()Unit2.doSomething()thực hiện, do đó, việc mong đợi mã kiểm tra đơn vị tiếp tục chạy mà không cần sửa đổi là điều tự nhiên.


5

Tôi muốn nói điều này có hai phần:

  1. Truy cập mặc định, "gói", hữu ích trong nhiều trường hợp, bởi vì các lớp không phải lúc nào cũng là đơn vị đóng gói tốt. Các đối tượng tổng hợp khác nhau trong đó một số đối tượng đóng vai trò là bộ sưu tập của các đối tượng khác, nhưng các mục không được sửa đổi công khai, bởi vì có một số bất biến trên toàn bộ bộ sưu tập, vì vậy bộ sưu tập cần có quyền truy cập cao vào các mục. C ++ có bạn bè, Java có quyền truy cập gói.
  2. Bây giờ, phạm vi truy cập "gói" về cơ bản là độc lập với phạm vi "lớp con" (được bảo vệ). Vì vậy, bạn cần các chỉ định truy cập bổ sung cho chỉ gói, lớp con và gói và lớp con. Phạm vi "gói" bị hạn chế nhiều hơn vì tập hợp các lớp trong một gói thường là xác định trong khi một lớp con có thể xuất hiện ở bất cứ đâu. Vì vậy, để đơn giản hóa, Java chỉ đơn giản bao gồm quyền truy cập gói trong quyền truy cập được bảo vệ và không có trình xác định bổ sung cho các lớp con nhưng không phải là gói. Mặc dù bạn hầu như luôn luôn nghĩ về protectedchính xác như vậy.

Sẽ không đơn giản hơn nếu chỉ protectedlà lớp con? Thành thật mà nói, trong một thời gian dài, tôi đã có ấn tượng rằng đó là hành vi
jramoyo

@jramoyo: Không, bởi vì bạn vẫn cần phải thực hiện hành vi kết hợp bằng cách nào đó, điều đó có nghĩa là một công cụ xác định khác.
Jan Hudec

6
@jramoyo - Trong C #, chỉ protectedlà lớp và lớp con và internallà thư viện / gói rộng. Nó cũng có protected internaltương đương với Java protected.
Bobson

@Bobson - cảm ơn, việc triển khai C # có vẻ như là một lựa chọn tốt hơn
jramoyo

5

IMHO, đây là một quyết định thiết kế tồi trong Java.

Chỉ cần suy đoán, nhưng tôi nghĩ rằng họ muốn các cấp truy cập được tiến hành nghiêm ngặt: riêng tư - "gói" - được bảo vệ - công khai. Họ không muốn có một hệ thống phân cấp, trong đó một số trường sẽ có sẵn cho gói nhưng không phải là các lớp con, một số có sẵn cho các lớp con nhưng không phải là gói và một số cả hai.

Tuy nhiên, theo quan điểm khiêm tốn của tôi, họ nên đi theo một cách khác: Đã nói rằng bảo vệ chỉ hiển thị cho lớp và các lớp con, và gói đó được hiển thị cho lớp, lớp con và gói.

Tôi thường có dữ liệu trong một lớp cần có thể truy cập được vào các lớp con, nhưng không phải là phần còn lại của gói. Tôi rất khó để nghĩ về một trường hợp ngược lại là đúng. Tôi đã có một vài lần hiếm hoi khi tôi có một nhóm các lớp liên quan đến nhau cần chia sẻ dữ liệu nhưng điều đó sẽ giữ dữ liệu đó an toàn từ bên ngoài gói. Vì vậy, không sao, đặt chúng trong một gói, v.v. Nhưng tôi chưa bao giờ gặp trường hợp tôi muốn gói đó chia sẻ dữ liệu, nhưng tôi muốn giữ nó khỏi các lớp con. Được rồi, tôi có thể tưởng tượng tình hình sắp tới. Tôi cho rằng nếu gói này là một phần của thư viện có thể được mở rộng bởi các lớp mà tôi không biết gì, vì nhiều lý do tương tự tôi sẽ làm cho dữ liệu trong một lớp riêng tư. Nhưng cách phổ biến hơn là chỉ muốn cung cấp dữ liệu cho lớp và trẻ em.

Có rất nhiều điều mà tôi giữ kín giữa tôi và con tôi và chúng tôi không chia sẻ với hàng xóm. Có rất ít tôi giữ kín giữa tôi và hàng xóm và không chia sẻ với con tôi. :-)


0

Một ví dụ điển hình xuất hiện ngay lập tức là các lớp tiện ích được sử dụng rất nhiều trong gói nhưng bạn không muốn truy cập công cộng (các cảnh phía sau hậu trường tải hình ảnh từ đĩa, các lớp xử lý / tạo / hủy, v.v. .) thay vì làm tất cả mọi thứ [tương đương Java friend access modifier or idiomtrong C++] của mọi tầng lớp mọi thứ khác là tự động có sẵn.


1
Các lớp tiện ích là một ví dụ về các lớp bên trong toàn bộ hơn là các thành viên của chúng.
Jan Hudec

0

Các trường hợp sử dụng cho bảo vệ truy cập / gói sửa đổi cũng tương tự như những người cho bạn bổ truy cập trong C ++.

Một trường hợp sử dụng là khi thực hiện mẫu Memento .

Đối tượng memento cần truy cập vào trạng thái bên trong của một đối tượng, để giữ nó, để phục vụ như một điểm kiểm tra cho các hoạt động hoàn tác.

Khai báo lớp trong cùng một gói là một trong những cách có thể để đạt được mẫu Memento, vì Java không có công cụ sửa đổi truy cập "bạn bè" .


1
Không, đối tượng memento không cần và không nên có quyền truy cập vào đối tượng. Nó là đối tượng nối tiếp chính nó vào vật lưu niệm và giải tuần tự hóa một lần nữa. Và bản thân vật lưu niệm chỉ là một túi tài sản câm. Không nên có nhiều hơn truy cập công cộng khác.
Jan Hudec

@JanHudec Tôi đã nói theo nghĩa đen "là -> một <- trong số những cách có thể để đạt được mô hình Memento" .
Tulains Córdova

Và một cách khác có thể để đạt được mô hình Memento là làm cho mọi thứ trở nên công khai. Đó là, tôi không thấy quan điểm của bạn.
Thomas Eding

-1

đối diện?

Rất hiếm khi cần truy cập như vậy, đó là lý do tại sao quyền truy cập mặc định rất hiếm khi được sử dụng. Nhưng đôi khi các khung công tác muốn nó cho mã được tạo, trong đó các lớp trình bao bọc được đặt trong cùng một gói tương tác trực tiếp với các lớp của bạn, đến các thành viên thay vì các trình truy cập vì lý do hiệu suất .. Hãy nghĩ đến GWT.


1
cảm ơn, bạn có một ví dụ không? âm thanh như thế có thể được thực hiện bởi một người truy cập "mặc định" thay vì "được bảo vệ"
jramoyo

-1

Hệ thống phân cấp đóng gói của Java được xác định rõ:

Lớp -> Gói -> Kế thừa

"Được bảo vệ" chỉ là một hình thức riêng tư yếu hơn so với gói mặc định theo quyết định của các nhà thiết kế Java. Quyền truy cập vào các mục mặc định gói được giới hạn trong một tập hợp con của các thực thể được phép truy cập các mục được bảo vệ.

Từ quan điểm toán học và triển khai có ý nghĩa rất lớn để có các bộ thực thể của bạn được phép truy cập vào những thứ được lồng vào nhau. (Bạn không thể lồng bộ truy cập gói của bạn bên trong bộ truy cập được bảo vệ của bạn vì các lớp được phép kế thừa từ các gói khác).

Theo quan điểm khái niệm để có một cái gì đó trong java.util trở nên "thân thiện" hơn với một lớp khác trong gói hơn là một thứ gì đó trong com.example.foo.bar đang phân lớp nó. Trong trường hợp trước, các lớp có khả năng được viết bởi cùng một tác giả hoặc ít nhất là các lập trình viên từ cùng một tổ chức.


1
những gì có nghĩa là "chỉ là một hình thức yếu ..." ?
gnat
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.