2016-02-20 13 views
5

Tôi đang triển khai mẫu khuôn mẫu trong Java. Chúng ta hãy giả định đoạn mã sau:Móc bảo vệ trong mẫu hoa văn

public abstract class A { 

    public final void work() { 
    doPrepare(); 
    doWork(); 
    } 

    protected abstract void doPrepare(); 

    protected abstract void doWork(); 
} 

public final class B extends A { 

    @Override 
    protected abstract void doPrepare() { /* do stuff here */ } 

    @Override 
    protected abstract void doWork() { /* do stuff here */ } 
} 

public final class Main { 

    public static void main(String[] args) { 
    B b = new B(); 
    b.work(); 
    } 
} 

Tôi coi đó là một vấn đề mà một là dễ dàng có thể vô tình gọi b.doWork() thay vì luôn gọi b.work(). Giải pháp thanh lịch nhất, nếu có thể, để "ẩn" các móc?

+0

"một là dễ dàng có thể vô tình gọi b.doWork()". Có thật không? Nó được bảo vệ, vì vậy chỉ có một hoặc B trường hợp có thể gọi nó. –

+0

Thật không may, vì nó được bảo vệ, tất cả các lớp từ gói có thể gọi nó. Chia gói là một cách để đi, mặc dù tôi muốn tránh có các gói 1 hoặc 2 lớp. –

+0

Không !! Bạn đang mô tả công cụ sửa đổi truy cập mặc định (ví dụ: không có gì). Được bảo vệ có nghĩa là "chỉ có tôi hoặc các lớp học mở rộng tôi mới có thể sử dụng" –

Trả lời

2

Điều đơn giản nhất và rõ ràng hơn bạn có thể làm là sử dụng phân cấp A -> B từ gói khác.

Nếu vì một lý do nào đó bạn không thể làm điều đó, thì bạn có thể sử dụng giao diện và tạo lớp học B bọc lớp thực sự xuống từ A. Một cái gì đó như thế này:

public interface Worker { 

    void work(); 
} 

public abstract class A implements Worker { 

    @Override 
    public final void work() { 
     doPrepare(); 
     doWork(); 
    } 

    protected abstract void doPrepare(); 

    protected abstract void doWork(); 
} 

public static class B implements Worker { // does not extend A, but implements Worker 

    private final A wrapped = new A() { // anonymous inner class, so it extends A 

     @Override 
     protected void doPrepare() { 
      System.out.println("doPrepare"); 
     } 

     @Override 
     protected void doWork() { 
      System.out.println("doWork"); 
     } 
    }; 

    @Override 
    public void work() { // delegate implementation to descendant from A 
     this.wrapped.work(); 
    } 
} 

Bằng cách này, bảo vệ phương pháp vẫn ẩn trong một lớp bên trong vô danh mà kéo dài từ A, mà là một thành viên chính thức của riêng B. Điều quan trọng là B không mở rộng A, nhưng thực hiện phương thức Worker của giao diện work() bằng cách ủy nhiệm cho lớp bên trong ẩn danh.Vì vậy, ngay cả trong trường hợp phương pháp work() của đang được gọi từ một lớp thuộc cùng một gói, các phương thức được bảo vệ sẽ không hiển thị.

B b = new B(); 
b.work(); // this method is accessible via the Work interface 

Output:

doPrepare 
doWork 
+0

Tôi đã thử và điều chỉnh mã của mình để làm theo cách tiếp cận này và nó hoạt động như một sự quyến rũ, cảm ơn bạn. Nó cũng có tác dụng với Generics. Nhược điểm duy nhất tôi đã tìm thấy là, đối với mỗi phương thức trong lớp trừu tượng A, bạn cần cung cấp một trình bao bọc; một cái gì đó bạn có thể tránh khi bạn sử dụng thừa kế. Nhưng điều này chắc chắn là một sự cân bằng dễ chịu. –

3

Bạn đang thực sự sử dụng mẫu khuôn mẫu. Có hai điều chính bạn có thể làm để "ẩn" chi tiết từ người dùng bên ngoài:

1) Hiểu bạn access modifiers:

    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  | 

ngắn của tin tặc quyết tâm bắt nạt con đường của họ trong quá khứ những biện pháp bảo vệ với sự phản ánh, những thể giữ các lập trình viên bình thường từ các phương thức gọi vô tình được giữ kín nhất. Các lập trình viên sẽ đánh giá cao điều này. Không ai muốn sử dụng API lộn xộn.

Hiện tại b.doWork()protected. Vì vậy, nó sẽ chỉ hiển thị trong main() nếu main() của bạn nằm trong lớp A (không phải), phân lớp B (không phải) hoặc trong cùng một gói (không rõ ràng, bạn không đăng bất kỳ manh mối nào về gói) mã của bạn nằm trong).

Điều này đặt ra câu hỏi, bạn đang cố gắng bảo vệ ai khi nhìn thấy b.doWork()? Nếu gói bạn đang tạo là thứ duy nhất bạn đang phát triển nó có thể không quá tệ. Nếu nhiều người đang dậm dẫm xung quanh trong gói này rối tung với nhiều lớp khác thì không có khả năng họ sẽ quen thuộc với các lớp học AB. Đây là trường hợp bạn không muốn tất cả mọi thứ treo ra nơi mọi người có thể nhìn thấy nó. Ở đây nó sẽ được tốt đẹp để chỉ cho phép truy cập lớp và phân lớp. Nhưng không có công cụ sửa đổi nào cho phép truy cập lớp con mà không cho phép truy cập gói. Miễn là bạn đang phân lớp protected là cách tốt nhất bạn có thể làm. Với ý nghĩ đó, đừng đi tạo các gói với số lượng lớn các lớp. Giữ các gói chặt chẽ và tập trung. Bằng cách đó hầu hết mọi người sẽ làm việc bên ngoài gói, nơi mọi thứ trông đẹp và đơn giản.

Tóm lại, nếu bạn muốn xem main() ngăn không cho gọi b.doWork() đặt main() vào gói khác.

package some.other.package 

public final class Main { 
... 
} 

2) Giá trị của lời khuyên tiếp theo này sẽ khó hiểu với mã hiện tại của bạn vì nó giải quyết các vấn đề sẽ chỉ xuất hiện nếu mã của bạn được mở rộng. Nhưng nó thực sự là quan hệ chặt chẽ với ý tưởng bảo vệ mọi người từ những điều nhìn thấy như b.doWork()

What does "program to interfaces, not implementations" mean?

Trong mã của bạn điều này có nghĩa là nó sẽ tốt hơn nếu chính trông như thế này:

public static void main(String[] args) { 
    A someALikeThingy = new B(); // <-- note use of type A 
    someALikeThingy.work();  //The name is silly but shows we've forgotten about B 
           //and know it isn't really just an A :) 
} 

Tại sao? Điều gì đang sử dụng loại A và loại B vấn đề? Vâng A gần với giao diện . Lưu ý, ở đây tôi không chỉ có nghĩa là những gì bạn nhận được khi bạn sử dụng từ khóa interface.Tôi có nghĩa là loại B là một thực hiện cụ thể của loại A và nếu tôi không hoàn toàn phải viết mã chống lại B Tôi thực sự không muốn vì ai biết những gì lạ không A điều B có. Điều này là khó hiểu ở đây vì mã trong chính ngắn & ngọt ngào. Ngoài ra, giao diện công khai của AB không phải tất cả khác nhau. Cả hai đều chỉ có một phương thức công khai work(). Hãy tưởng tượng rằng main() dài và phức tạp, Hãy tưởng tượng B có tất cả các phương thức không phải là A treo nó. Bây giờ hãy tưởng tượng bạn cần tạo ra một lớp C mà bạn muốn chính xử lý. Nó sẽ không được an ủi để thấy rằng trong khi chính được cho ăn một B rằng như xa như mọi dòng trong chính biết rằng B chỉ là một số đơn giản A như điều. Ngoại trừ một phần sau new điều này có thể đúng và giúp bạn không làm bất cứ điều gì để main() nhưng cập nhật rằng new. Thật vậy, đây không phải là lý do tại sao sử dụng một ngôn ngữ gõ mạnh mẽ đôi khi có thể được tốt đẹp?

<rant>
Nếu bạn nghĩ rằng thậm chí cập nhật phần đó sau new với B() là quá nhiều công việc bạn không cô đơn. Đó là nỗi ám ảnh nhỏ đặc biệt là những gì đã cho chúng tôi dependency injection movement mà sinh ra một loạt các khuôn khổ đã thực sự nhiều hơn về tự động hóa xây dựng đối tượng hơn là một tiêm đơn giản mà bất kỳ nhà xây dựng có thể cho phép bạn làm.
</rant >

Cập nhật:

tôi thấy từ ý kiến ​​của bạn mà bạn không muốn tạo ra các gói chặt nhỏ. Trong trường hợp đó, hãy xem xét việc bỏ qua mẫu mẫu dựa trên kế thừa dựa trên mẫu chiến lược dựa trên thành phần. Bạn vẫn nhận được đa hình. Nó chỉ không xảy ra miễn phí. Ít hơn viết mã và gián tiếp. Nhưng bạn có thể thay đổi trạng thái ngay cả khi chạy.

Điều này có vẻ phản trực giác vì bây giờ doWork() phải được công khai. Loại bảo vệ gì vậy? Giờ thì bạn có thể ẩn hoàn toàn B phía sau hoặc ngay cả bên trong AHandler. Đây là một số lớp mặt tiền thân thiện với con người nắm giữ những gì từng là mã mẫu của bạn nhưng chỉ phơi bày work(). A chỉ có thể là interface do đó AHandler vẫn không biết nó có số B. Cách tiếp cận này là phổ biến với đám đông tiêm phụ thuộc nhưng chắc chắn không có sức hấp dẫn phổ quát. Một số làm điều đó một cách mù quáng mỗi lần làm phiền nhiều. Bạn có thể đọc thêm tại đây: Prefer composition over inheritance?

+0

Cảm ơn bạn đã có câu trả lời đầy đủ. Tôi sẽ cố gắng và giải quyết tất cả các ý tưởng: 1) Điều chỉnh truy cập và cấu trúc gói là điều đầu tiên tôi đã xem xét từ lâu trước khi đăng câu hỏi. Gói của tôi đã nhỏ và việc chia nhỏ nó sẽ cung cấp cho tôi các gói 1- hoặc 2 lớp như tôi đã đề cập trong một trong các nhận xét trước của tôi. Đây không phải là giải pháp trong trường hợp hiện tại của tôi, nhưng có thể là cho người khác trong tương lai. –

+0

2) Đối với cách tiếp cận giao diện, nó có hai nhược điểm. Thứ nhất, nó không hoạt động tốt với generics (tôi có thể cung cấp một ví dụ). Thứ hai, nó ngăn cản bạn viết: 'Kết quả r = new B (args) .work();', mà tôi coi là đẹp hơn phần nào bị ép buộc 'A a = new B (args); Kết quả r = a.work(); '. Đây là một cái gì đó tôi đã cố gắng và vẫn đang tìm kiếm một cái gì đó tốt hơn, mặc dù nó có thể được chấp nhận đối với một số người. –

+0

3) Thành phần thực sự là câu trả lời. Tôi đã tăng quyền thừa kế do ngữ nghĩa của các lớp học của tôi và quên xem xét tùy chọn này. Tôi đánh dấu câu trả lời của ông Schaffner là chính xác vì nó cung cấp một ví dụ, và giữ truy cập hạn chế (được bảo vệ) đối với các móc nối; nhưng tôi khuyên người khác nên xem các liên kết bạn đã đăng. –

Các vấn đề liên quan