2009-12-13 33 views

Trả lời

68

Phụ thuộc thông tư giữa các lớp không nhất thiết là có hại. Thật vậy, trong một số trường hợp, chúng được mong muốn. Ví dụ, nếu ứng dụng của bạn xử lý vật nuôi và chủ sở hữu của chúng, bạn sẽ mong đợi lớp Pet có một phương thức để sở hữu chủ sở hữu của vật nuôi và lớp Chủ sở hữu có một phương thức trả về danh sách vật nuôi. Chắc chắn, điều này có thể làm cho việc quản lý bộ nhớ trở nên khó khăn hơn (bằng ngôn ngữ không phải GC).Nhưng nếu tính tròn có vốn có trong vấn đề, thì cố gắng loại bỏ nó có lẽ sẽ dẫn đến nhiều vấn đề hơn.

Mặt khác, phụ thuộc vòng tròn giữa các mô-đun có hại. Nó thường chỉ ra một cấu trúc mô-đun kém suy nghĩ, và/hoặc không tuân theo mô đun hóa ban đầu. Nói chung, một cơ sở mã với các phụ thuộc chéo không kiểm soát được sẽ khó hiểu hơn và khó bảo trì hơn so với một cấu trúc mô-đun sạch, lớp. Nếu không có các mô-đun tốt, có thể khó dự đoán tác động của thay đổi hơn. Và điều đó làm cho việc bảo trì khó khăn hơn và dẫn đến "phân rã mã" do việc vá lỗi.

(Ngoài ra, xây dựng các công cụ như Maven sẽ không xử lý module (hiện vật) với phụ thuộc vòng tròn.)

+6

Có lẽ bảng mối quan hệ SQL cổ điển có thể được sử dụng để loại bỏ chu kỳ phụ thuộc giữa vật nuôi và chủ sở hữu? Một đối tượng giữ tham chiếu đến các mối quan hệ chủ sở hữu/thú cưng và các phương thức hỗ trợ thiết lập để trả về các tập con con theo cả hai hướng (cũng như các phương thức để thêm và loại bỏ các mối quan hệ đó). Trong chương trình đó, các lớp học về vật nuôi và chủ sở hữu phụ thuộc vào lớp mối quan hệ này. Tuy nhiên, nó không phải phụ thuộc vào một trong hai lớp sử dụng nó. –

+7

@Jim - Tôi không nói rằng bạn không thể loại bỏ sự phụ thuộc vòng tròn giữa các lớp. Ví dụ, bạn có thể làm điều đó bằng cách khai báo (get) 'getOwner()' để trả về một 'Object' thay vì' Owner'. Nhưng, IMO loại điều này có khả năng dẫn đến các vấn đề tồi tệ hơn sự phụ thuộc vòng tròn mà bạn đã loại bỏ. –

7

Bởi vì bây giờ chúng thực sự là một đối tượng duy nhất. Bạn không thể kiểm tra một trong hai cách ly.

Nếu bạn sửa đổi một, có khả năng bạn cũng ảnh hưởng đến bạn đồng hành của nó.

+0

Điều gì xảy ra nếu chúng triển khai cùng một giao diện? – Pierreten

+0

@Pierreten: Thậm chí sau đó, đó là một ý tưởng tồi vì lý do tương tự. – RCIX

+3

@RCIX - hãy cẩn thận. Bạn vừa loại bỏ đồ thị, một số cây và danh sách được liên kết kép. – Kobi

6

Từ Wikipedia:

Thông tư phụ thuộc có thể gây ra nhiều tác dụng không mong muốn trong các chương trình phần mềm. Hầu hết các vấn đề từ phần mềm quan điểm thiết kế là chặt chẽ khớp nối của phụ thuộc lẫn nhau mô-đun làm giảm hoặc làm cho không thể sử dụng lại riêng biệt mô-đun đơn .

Thông tư phụ thuộc có thể gây ra hiệu ứng domino khi một địa phương nhỏ sự thay đổi trong một module lây lan vào module khác và có mong muốn toàn cầu hiệu ứng (lỗi chương trình, biên soạn lỗi). Phụ thuộc theo quy định có thể cũng dẫn đến việc thu thập vô hạn hoặc các lỗi không mong muốn khác.

Thông tư phụ thuộc cũng có thể gây ra rò rỉ bộ nhớ bằng cách ngăn chặn một số rất thô sơ rác tự động nhà sưu tập (những người sử dụng tài liệu tham khảo đếm) từ deallocating không sử dụng đối tượng.

+1

Đây là thông tin về tham chiếu lắp ráp hơn so với tham chiếu đối tượng. –

+2

tuyệt vời như thế nào 'meta'. trang SO này có tham chiếu vòng tròn với Wikipedia. Câu trả lời này trích dẫn Wikipedia và trang Wikipedia liên kết lại ở đây: https://en.wikipedia.org/wiki/Circular_dependency (xem "các liên kết bên ngoài" trên trang wiki đó) – pestophagous

2

Điều này làm giảm khả năng đọc mã. Và từ những phụ thuộc vòng tròn đến mã spaghetti chỉ có một bước nhỏ.

4

Đối tượng như vậy có thể khó tạo và hủy, bởi vì để thực hiện hoặc không nguyên tử, bạn phải vi phạm tính toàn vẹn tham chiếu để tạo/hủy đầu tiên, cái kia (ví dụ, cơ sở dữ liệu SQL của bạn có thể tại đây). Nó có thể gây nhầm lẫn cho bộ thu gom rác của bạn. Perl 5, sử dụng tính toán tham chiếu đơn giản để thu gom rác, không thể (không có sự trợ giúp) để rò rỉ bộ nhớ. Nếu hai đối tượng có các lớp khác nhau thì chúng được ghép chặt và không thể tách rời. Nếu bạn có một trình quản lý gói để cài đặt các lớp đó thì phụ thuộc vòng tròn sẽ lan truyền đến nó. Phải biết cài đặt cả hai góitrước khi kiểm tra chúng, cái mà (nói như người duy trì hệ thống xây dựng) là một PITA.

Điều đó nói rằng, tất cả những điều này đều có thể được khắc phục và thường cần thiết để có dữ liệu hình tròn. Thế giới thực không được tạo thành từ các đồ thị được chỉ dẫn gọn gàng. Nhiều đồ thị, cây cối, địa ngục, một danh sách liên kết đôi là hình tròn.

-2

Bộ thu gom rác .NET có thể xử lý các tham chiếu vòng tròn để không có sự rò rỉ rò rỉ bộ nhớ đối với các ứng dụng hoạt động trên .NET framework.

+1

"không có rò rỉ rò rỉ bộ nhớ cho các ứng dụng" Tôi 'm không chắc chắn nếu tuyên bố đó là rất đúng sự thật. – strager

+0

Tôi đồng ý strager; espcecially khi nói đến quên để tách xử lý sự kiện – Pierreten

+0

rò rỉ bộ nhớ phạm vi ứng dụng có thể trong .NET; tuy nhiên, loại được mô tả ở đây * nên * được xử lý bởi GC, như đã nêu, Trình xử lý sự kiện là một câu chuyện khác. – Nate

1

Dưới đây là một vài ví dụ có thể giúp minh họa cho lý do tại sao phụ thuộc vòng tròn là xấu.

Bài toán số 1: Nội dung nào được khởi tạo/xây dựng trước?

Hãy xem xét ví dụ sau:

class A 
{ 
    public A() 
    { 
    myB.DoSomething(); 
    } 

    private B myB = new B(); 
} 

class B 
{ 
    public B() 
    { 
    myA.DoSomething(); 
    } 

    private A myA = new A(); 
} 

nào constructor được gọi là đầu tiên? Thực sự không có cách nào để chắc chắn vì nó hoàn toàn mơ hồ. Một hoặc các phương thức DoSomething khác sẽ được gọi trên một đối tượng chưa được khởi tạo. Kết quả là hành vi không chính xác và rất có thể là một ngoại lệ được nâng lên. Có nhiều cách xung quanh vấn đề này, nhưng tất cả chúng đều xấu xí và tất cả chúng đều yêu cầu khởi tạo non-constructor.

Vấn đề # 2:

Trong trường hợp này, tôi đã đổi thành một tổ chức phi quản lý C++ ví dụ bởi vì việc thực hiện NET, do thiết kế, sẽ ẩn những vấn đề xa bạn. Tuy nhiên, trong ví dụ sau, vấn đề sẽ trở nên khá rõ ràng. Tôi nhận thức rõ rằng .NET không thực sự sử dụng tính toán tham chiếu dưới mui xe để quản lý bộ nhớ. Tôi đang sử dụng nó ở đây chỉ để minh họa cho vấn đề cốt lõi. Cũng lưu ý rằng tôi đã chứng minh ở đây một giải pháp khả thi cho vấn đề # 1.

class B; 

class A 
{ 
public: 
    A() : Refs(1) 
    { 
    myB = new B(this); 
    }; 

    ~A() 
    { 
    myB->Release(); 
    } 

    int AddRef() 
    { 
    return ++Refs; 
    } 

    int Release() 
    { 
    --Refs; 
    if(Refs == 0) 
     delete(this); 
    return Refs; 
    } 

    B *myB; 
    int Refs; 
}; 

class B 
{ 
public: 
    B(A *a) : Refs(1) 
    { 
    myA = a; 
    a->AddRef(); 
    } 

    ~B() 
    { 
    myB->Release(); 
    } 

    int AddRef() 
    { 
    return ++Refs; 
    } 

    int Release() 
    { 
    --Refs; 
    if(Refs == 0) 
     delete(this); 
    return Refs; 
    } 

    A *myA; 
    int Refs; 
}; 

// Somewhere else in the code... 
... 
A *localA = new A(); 
... 
localA->Release(); // OK, we're done with it 
... 

Thoạt nhìn, người ta có thể nghĩ rằng mã này là chính xác. Mã đếm tham chiếu khá đơn giản và thẳng. Tuy nhiên, mã này dẫn đến rò rỉ bộ nhớ. Khi A được xây dựng, ban đầu nó có một số tham chiếu là "1". Tuy nhiên, biến myB đóng gói tăng số lượng tham chiếu, cho nó một số "2". Khi localA được giải phóng, số đếm được giảm đi, nhưng chỉ quay lại "1". Do đó, đối tượng là trái treo và không bao giờ bị xóa.

Như tôi đã đề cập ở trên, .NET không thực sự sử dụng tính tham chiếu để thu thập rác của nó. Nhưng nó sử dụng các phương thức tương tự để xác định nếu một đối tượng vẫn đang được sử dụng hoặc nếu nó là OK để xóa nó, và hầu như tất cả các phương thức như vậy có thể bị nhầm lẫn bởi các tham chiếu vòng tròn. Trình thu gom rác .NET tuyên bố có thể xử lý điều này, nhưng tôi không chắc tôi tin tưởng nó vì đây là một vấn đề rất hóc búa. Đi, mặt khác, được xung quanh vấn đề bằng cách đơn giản là không cho phép tham khảo vòng tròn ở tất cả. Mười năm trước, tôi đã ưa thích phương pháp .NET cho sự linh hoạt của nó. Những ngày này, tôi thấy mình thích cách tiếp cận Go cho sự đơn giản của nó.

+2

Bộ sưu tập rác của .NET sử dụng thuật toán đánh dấu và quét bắt đầu từ tất cả các gốc đã biết (thành viên tĩnh, vv) và đánh dấu mọi tham chiếu có thể truy cập từ đó. Bất kỳ tài liệu tham khảo nào không thể truy cập được (dù chúng có liên quan đến nhau hay không) đều không được đánh dấu, và do đó trở thành đủ điều kiện để thu thập. Tại sao bạn thấy điều này có vấn đề với tham chiếu vòng tròn? –

+0

bạn không thể chỉ định tham chiếu đối tượng cho null khi bạn không cần nó nữa? –

56

Tham chiếu thông tư không phải lúc nào cũng có hại - có một số trường hợp sử dụng mà chúng có thể khá hữu ích. Danh sách được liên kết kép, mô hình biểu đồ và ngữ pháp ngôn ngữ máy tính sẽ đến với bạn. Tuy nhiên, như một thực tế chung, có một số lý do tại sao bạn có thể muốn tránh tham chiếu vòng tròn giữa các đối tượng.

  1. Dữ liệu và đồ thị nhất quán. Cập nhật các đối tượng với tham chiếu vòng tròn có thể tạo ra những thách thức trong việc đảm bảo rằng tại tất cả các điểm trong thời gian mối quan hệ giữa các đối tượng là hợp lệ. Loại vấn đề này thường phát sinh trong việc triển khai mô hình hóa đối tượng-quan hệ, ở đây không có gì lạ khi tìm thấy các tham chiếu hai chiều, tròn giữa các thực thể.

  2. Đảm bảo hoạt động nguyên tử. Đảm bảo rằng các thay đổi đối với cả hai đối tượng trong tham chiếu vòng tròn là nguyên tử có thể trở nên phức tạp - đặc biệt khi đa luồng có liên quan. Đảm bảo tính nhất quán của một đồ thị đối tượng có thể truy cập từ nhiều luồng yêu cầu cấu trúc đồng bộ hóa đặc biệt và các hoạt động khóa để đảm bảo rằng không có chuỗi nào nhìn thấy một tập hợp thay đổi không đầy đủ.

  3. Thử thách tách vật lý. Nếu hai lớp khác nhau A và B tham chiếu lẫn nhau theo kiểu vòng tròn, có thể trở nên khó khăn để tách các lớp này thành các hội đồng độc lập. Nó chắc chắn có thể tạo ra một hội đồng thứ ba với các giao diện IA và IB mà A và B thực hiện; cho phép mỗi tham chiếu khác thông qua các giao diện đó. Cũng có thể sử dụng các tham chiếu được nhập sai (ví dụ: đối tượng) như một cách để phá vỡ phụ thuộc vòng tròn, nhưng sau đó truy cập vào phương thức và thuộc tính của đối tượng như vậy không thể dễ dàng truy cập - có thể đánh bại mục đích có tham chiếu.

  4. Ép buộc tham chiếu vòng không thay đổi. Các ngôn ngữ như C# và VB cung cấp từ khóa để cho phép các tham chiếu trong một đối tượng không thay đổi (chỉ đọc). Các tham chiếu không thể thay đổi cho phép một chương trình để đảm bảo rằng một tham chiếu tham chiếu đến cùng một đối tượng trong suốt vòng đời của đối tượng. Thật không may, không dễ sử dụng cơ chế bất biến do trình biên dịch thực thi để đảm bảo rằng các tham chiếu vòng tròn không thể thay đổi được. Nó chỉ có thể được thực hiện nếu một đối tượng instantiates khác (xem ví dụ C# dưới đây).

    class A 
    { 
        private readonly B m_B; 
        public A(B other) { m_B = other; } 
    } 
    
    class B 
    { 
        private readonly A m_A; 
        public A() { m_A = new A(this); } 
    } 
    
  5. Khả năng đọc và bảo trì chương trình. Tham chiếu thông tư vốn đã dễ vỡ và dễ vỡ. Điều này xuất phát một phần từ thực tế là mã đọc và hiểu bao gồm các tham chiếu vòng tròn khó hơn mã để tránh chúng. Đảm bảo rằng mã của bạn dễ hiểu và duy trì giúp tránh các lỗi và cho phép thay đổi được thực hiện dễ dàng và an toàn hơn. Đối tượng với tham chiếu vòng tròn khó kiểm tra đơn vị hơn vì chúng không thể được kiểm tra cách ly với nhau.

  6. Quản lý toàn bộ đối tượng. Trong khi bộ thu gom rác của .NET có khả năng xác định và xử lý các tham chiếu vòng tròn (và xử lý chính xác các đối tượng đó), không phải tất cả ngôn ngữ/môi trường đều có thể. Trong các môi trường sử dụng tính toán tham chiếu cho lược đồ thu gom rác của chúng (ví dụ: VB6, Objective-C, một số thư viện C++), có thể tham chiếu vòng tròn dẫn đến rò rỉ bộ nhớ. Vì mỗi đối tượng nắm giữ nhau, số lượng tham chiếu của chúng sẽ không bao giờ đạt đến 0 và do đó sẽ không bao giờ trở thành ứng cử viên cho việc thu thập và dọn dẹp.

1

Hoàn toàn bình thường khi có đối tượng có tham chiếu vòng tròn, ví dụ: trong một mô hình miền với các liên kết hai chiều. Một ORM với một thành phần truy cập dữ liệu được viết đúng cách có thể xử lý điều đó.

1

Tham khảo sách của Lakos, trong thiết kế phần mềm C++, phụ thuộc vật lý theo chu kỳ là không mong muốn.Có một số lý do:

  • Làm cho chúng khó kiểm tra và không thể sử dụng lại độc lập.
  • Điều này khiến người khác khó hiểu và duy trì.
  • Điều này sẽ làm tăng chi phí liên kết.
1

Tham chiếu thông tư có vẻ là trường hợp mô hình miền hợp pháp. Một ví dụ là Hibernate và nhiều công cụ ORM khác khuyến khích sự liên kết chéo này giữa các thực thể để cho phép điều hướng hai hướng. Ví dụ điển hình trong một hệ thống đấu giá trực tuyến, người bán có quyền duy trì một tham chiếu đến Danh sách các thực thể mà Người đó đang bán. Và mỗi mục có thể duy trì một tham chiếu đến người bán tương ứng của nó.

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