2010-09-05 38 views
25

Tôi có các lớp học sau đây.Phụ thuộc thông tư trong các lớp học java

public class B 
{ 
    public A a; 

    public B() 
    { 
     a= new A(); 
     System.out.println("Creating B"); 
    } 
} 

public class A 
{ 
    public B b; 

    public A() 
    { 
     b = new B(); 
     System.out.println("Creating A"); 
    } 

    public static void main(String[] args) 
    { 
     A a = new A(); 
    } 
} 

Như có thể thấy rõ ràng, có một phụ thuộc vòng tròn giữa các lớp. nếu tôi cố gắng chạy lớp A, cuối cùng tôi sẽ nhận được StackOverflowError.

Nếu biểu đồ phụ thuộc được tạo, trong đó các nút là các lớp, thì phụ thuộc này có thể dễ dàng xác định (ít nhất là đối với biểu đồ có vài nút). Vậy tại sao JVM không xác định được điều này, ít nhất là khi chạy? Thay vì ném StackOverflowError, JVM ít nhất có thể đưa ra cảnh báo trước khi bắt đầu thực hiện.

[Cập nhật] Một số ngôn ngữ không thể có phụ thuộc vòng tròn, vì sau đó mã nguồn sẽ không được tạo. Ví dụ: see this question và câu trả lời được chấp nhận. Nếu phụ thuộc vòng tròn là một mùi thiết kế cho C# thì tại sao nó không dành cho Java? Chỉ vì Java có thể (biên dịch mã với phụ thuộc vòng tròn)?

[update2] Đã tìm thấy jCarder. Theo trang web, nó tìm thấy deadlocks tiềm năng bằng cách tự động thiết lập mã byte Java và tìm kiếm các chu kỳ trong đồ thị đối tượng. Bất cứ ai có thể giải thích làm thế nào công cụ tìm thấy các chu kỳ?

+0

Tại sao bạn mong đợi để có được một cảnh báo về vấn đề này? Bạn đã đọc một nơi nào đó mà JVM sẽ làm điều này cho bạn? – Cratylus

+1

Các loại vấn đề là rất dễ dàng cho các nhà phát triển để phát hiện và đầu tiên. JVM có xu hướng cảnh báo một vấn đề bout bạn không thể phát hiện dễ dàng, giống như một tệp lớp tham nhũng. –

+0

Tôi thích cách chỉ 2 trong số 5 câu trả lời (tính đến thời điểm tôi viết bài này) thực sự trả lời câu hỏi của bạn: 'tại sao trình biên dịch không phát hiện và cảnh báo về vấn đề tiềm năng'. Và cả 2 đều không được bình chọn nhiều nhất (một lần nữa, ít nhất là vào thời điểm tôi viết bài này). –

Trả lời

22

Phương thức khởi tạo của lớp A gọi hàm khởi tạo của lớp B. Hàm tạo của lớp B gọi hàm khởi tạo của lớp A. Bạn có cuộc gọi đệ quy vô hạn, đó là lý do bạn kết thúc có một StackOverflowError.

Hỗ trợ Java có phụ thuộc vòng tròn giữa các lớp, vấn đề ở đây chỉ liên quan đến các nhà thầu gọi cho nhau.

Bạn có thể thử với một cái gì đó như:

A a = new A(); 
B b = new B(); 

a.setB(b); 
b.setA(a); 
+5

Nhưng không phải là phụ thuộc vòng tròn xấu? Nếu bạn thực sự có phụ thuộc vòng tròn trong mã (như ví dụ bạn đã đưa ra), nó không phải là một chỉ báo của thiết kế xấu? Nếu có, thì tại sao java hỗ trợ nó? Nếu không, sau đó bạn có thể chỉ cho tôi một số trường hợp thiết kế liên quan đến phụ thuộc vòng tròn được ưa thích? – athena

+0

Làm thế nào về nhà sản xuất/người tiêu dùng hoặc bất kỳ tình huống gọi lại/sự kiện nào? Một cái gì đó như thế này sẽ kết thúc xảy ra, mặc dù có lẽ không theo nghĩa đen tùy thuộc giữa hai lớp học. Có lẽ giao diện. –

+2

Nó không thực sự rõ ràng những gì 'phụ thuộc' có nghĩa là trong trường hợp này. Phụ thuộc thường được dịch sang 'X' cần * xảy ra * trước' Y'. –

10

Nó không nhất thiết phải dễ như trong ví dụ của bạn. Tôi tin rằng việc giải quyết vấn đề này sẽ bằng với việc giải quyết halting problem mà - như chúng ta đều biết - là không thể.

+0

Tôi đồng ý, xác định xem có sự phụ thuộc vòng tròn cho các trường hợp phức tạp có thể không khả thi hay không. Nhưng, chúng ta có thể ước tính, trong trường hợp đó JVM có thể chỉ ra rằng có một phụ thuộc vòng tròn tiềm năng. Sau đó, nhà phát triển có thể xem lại mã. – athena

+0

Tôi nghĩ rằng hầu hết các trường hợp một trình biên dịch có thể phát hiện bởi xấp xỉ __in thời gian hợp lý__ là những người nhảy ngay vào bạn (như ví dụ trong câu hỏi của bạn). Có điều này như là một yêu cầu sẽ làm cho văn bản các trình biên dịch rất khó khăn trong khi đạt được ít. – musiKk

+1

JVM xấp xỉ thử nghiệm này, thông qua giới hạn ngăn xếp. Một mô hình thực hiện mạnh mẽ hơn với một ngăn xếp bất tận sẽ đơn giản là không dừng lại; giới hạn khung stack là một cơ chế mong muốn để tìm chính xác loại vấn đề này. Tôi cho rằng bạn hầu như không bao giờ gặp phải tình trạng tràn ngăn xếp mà không phải là sự phụ thuộc vòng tròn. –

11

của nó hoàn toàn hợp lệ trong Java để có một mối quan hệ vòng tròn giữa 2 lớp (mặc dù câu hỏi có thể được yêu cầu về thiết kế), tuy nhiên trong trường hợp của bạn, bạn có bất thường hành động của mỗi cá thể tạo một thể hiện của đối tượng khác trong hàm tạo của nó (đây là nguyên nhân thực sự của StackOverflowError).

Mẫu đặc biệt này được gọi là đệ quy lẫn nhau, trong đó bạn có 2 phương thức A và B (một hàm tạo chủ yếu là trường hợp đặc biệt của phương thức) và A gọi các cuộc gọi B và B A. Phát hiện một vòng lặp vô hạn trong mối quan hệ giữa 2 phương pháp này là có thể trong trường hợp tầm thường (một trong những bạn đã cung cấp), nhưng giải quyết nó cho chung là giống như việc giải quyết các vấn đề ngăn chặn. Cho rằng giải quyết vấn đề dừng là không thể, người khiếu nại thường không bận tâm cố gắng ngay cả đối với các trường hợp đơn giản.

Có thể bao gồm một vài trường hợp đơn giản bằng cách sử dụng mẫu FindBugs, nhưng sẽ không đúng cho tất cả các trường hợp.

0

Cách giải quyết tương tự đối với getters/setters bằng cách sử dụng bố cục và phun xây dựng cho các phụ thuộc. Điều quan trọng cần lưu ý là các đối tượng không tạo ra cá thể cho các lớp khác, chúng được truyền vào (còn gọi là tiêm).

public interface A {} 
public interface B {} 

public class AProxy implements A { 
    private A delegate; 

    public void setDelegate(A a) { 
     delegate = a; 
    } 

    // Any implementation methods delegate to 'delegate' 
    // public void doStuff() { delegate.doStuff() } 
} 

public class AImpl implements A { 
    private final B b; 

    AImpl(B b) { 
     this.b = b; 
    } 
} 

public class BImpl implements B { 
    private final A a; 

    BImpl(A a) { 
     this.a = a; 
    } 
} 

public static void main(String[] args) { 
    A proxy = new AProxy(); 
    B b = new BImpl(proxy); 
    A a = new AImpl(b); 
    proxy.setDelegate(a); 
} 
+0

Tôi không thấy cách giải quyết vấn đề này, vì bạn dựa vào người triển khai B để không gọi bất kỳ phương thức nào trên A trong hàm tạo B khi A là tiêm. Làm như vậy sẽ gây ra lời gọi phương thức xảy ra trên proxy, mà cuối cùng sẽ dẫn đến một NullPointerException vì bạn muốn proxy các cuộc gọi đó trên A (mà sẽ là null). – marchaos

+0

Điều tương tự có thể được nói cho trường hợp getter/setter. Điểm ở đây là để tạo một cá thể, một tham số phải được truyền cho hàm tạo. Ví dụ proxy ở đây cho phép một "ràng buộc trễ" phá vỡ sự phụ thuộc vòng tròn. Nó có sạch không? Không. Đây có phải là giải pháp khả thi không? Có, trong ngắn hạn nhưng chắc chắn không phải trong dài hạn. Giải pháp đúng sẽ là một thiết kế tốt hơn, nơi A và B không biết gì về nhau. ví dụ: C c = new C (A, B) – gpampara

3

Nếu bạn thực sự có một trường hợp sử dụng như thế này, bạn có thể tạo các đối tượng theo yêu cầu (uể oải) và sử dụng một getter:

public class B 
{ 
    private A a; 

    public B() 
    { 
     System.out.println("Creating B"); 
    } 

    public A getA() 
    { 
     if (a == null) 
     a = new A(); 

     return a; 
    } 
} 

(và tương tự cho các lớp A). Vì vậy, chỉ các đối tượng cần thiết mới được tạo nếu bạn làm:

a.getB().getA().getB().getA() 
+0

Không thực hành tốt nhất nhưng là một ý tưởng tuyệt vời! – ozma

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