2009-01-21 34 views
14

Đây là những gì tôi hiểu về IDisposable và finalizers từ "CLR thông qua C#", "Effective C#" và các nguồn lực khác:Singleton với finalizer nhưng không IDisposable

  • IDisposable là để dọn dẹp quản lý và các nguồn lực không được quản lý tất định.
  • Các lớp chịu trách nhiệm về tài nguyên không được quản lý (ví dụ: xử lý tệp) nên triển khai IDisposable và cung cấp trình xác nhận để đảm bảo rằng chúng được làm sạch ngay cả khi mã khách hàng không gọi Dispose() trên cá thể.
  • Các lớp chịu trách nhiệm đối với tài nguyên được quản lý chỉ không bao giờ triển khai trình kết thúc.
  • Nếu bạn có trình kết thúc thì bạn phải triển khai IDisposable (điều này cho phép mã khách hàng thực hiện đúng và gọi Dispose(), trong khi trình hoàn tất ngăn chặn tài nguyên bị rò rỉ nếu họ quên).

Mặc dù tôi hiểu lý do và đồng ý với tất cả các điều trên, có một trường hợp tôi nghĩ rằng cần phải phá vỡ các quy tắc này: một lớp đơn lẻ chịu trách nhiệm về tài nguyên không được quản lý (chẳng hạn như cung cấp một điểm truy cập vào các tệp cụ thể).

Tôi tin rằng có một phương thức Dispose() trên một singleton vì trường hợp singleton nên sống cho ứng dụng và nếu bất kỳ mã khách hàng nào gọi Dispose() thì bạn sẽ bị nhét. Tuy nhiên, bạn muốn một finalizer để khi ứng dụng được unloaded finalizer có thể dọn sạch các tài nguyên không được quản lý.

Vì vậy, có một lớp singleton với trình hoàn thiện không thực hiện IDisposable có vẻ như tôi là một điều hợp lý để làm, nhưng kiểu thiết kế này chống lại những gì tôi hiểu là thực hành tốt nhất.

Đây có phải là cách tiếp cận hợp lý không? Nếu không, tại sao không và những lựa chọn thay thế vượt trội là gì?

Trả lời

3

Nếu tài nguyên không được quản lý chỉ được phát hành khi thoát khỏi ứng dụng, bạn thậm chí không cần phải lo lắng với trình hoàn thiện vì quá trình tải xuống sẽ xử lý vấn đề này cho bạn.

Nếu bạn có nhiều miền ứng dụng và bạn muốn xử lý việc tải miền ứng dụng là một vấn đề có thể xảy ra nhưng có thể là vấn đề bạn không cần quan tâm. Tôi thứ hai những người nói rằng thiết kế này có thể không phải là điều đúng để làm (và sẽ làm cho nó khó khăn hơn để sửa chữa là sau đó bạn thấy rằng bạn thực sự cần hai trường hợp) Tạo đối tượng (hoặc một đối tượng tải wrapper lười biếng trong điểm nhập cảnh của bạn và chuyển nó qua mã tới nơi cần thiết để làm rõ ai chịu trách nhiệm cung cấp nó cho ai, sau đó bạn được tự do thay đổi quyết định chỉ sử dụng một hiệu ứng với phần còn lại của mã (trong đó sử dụng những gì nó được đưa ra)

4

Lần đầu tiên tôi đề cập đến các mẫu thiết kế hướng đối tượng và hậu quả của chúng không phải lúc nào cũng ảnh hưởng đến mọi quyết định ngôn ngữ, ngay cả trong ngôn ngữ hướng đối tượng. Bạn chắc chắn có thể tìm thấy các mẫu thiết kế cổ điển dễ thực hiện hơn trong một ngôn ngữ (Smalltalk) như trái ngược với một ngôn ngữ khác (C++).

Điều đó đang được nói, tôi không chắc chắn rằng tôi đồng ý với tiền đề rằng một cá thể đơn lẻ chỉ nên được xử lý ở cuối đơn đăng ký. Không có gì trong các mô tả mẫu thiết kế mà tôi đã đọc cho Singleton (hoặc Design Patterns: Elements of reusable Object-Oriented Software) đề cập đến đây là thuộc tính của mẫu này. Một singleton nên đảm bảo rằng chỉ có một thể hiện của lớp tồn tại tại bất kỳ thời điểm nào; điều đó không ngụ ý rằng nó phải tồn tại miễn là ứng dụng tồn tại.

Tôi có cảm giác rằng trong thực tế, nhiều người độc thân tồn tại trong hầu hết cuộc đời của một ứng dụng. Tuy nhiên, hãy xem xét một ứng dụng sử dụng kết nối TCP để giao tiếp với một máy chủ, nhưng cũng có thể tồn tại trong một chế độ ngắt kết nối. Khi được kết nối, bạn sẽ muốn một singleton duy trì thông tin kết nối và trạng thái kết nối. Sau khi bị ngắt kết nối, bạn có thể muốn giữ cùng một singleton đó - hoặc bạn có thể vứt bỏ singleton. Trong khi một số người có thể lập luận rằng nó có ý nghĩa hơn để giữ singleton (và thậm chí tôi có thể là một trong số họ), không có gì trong mẫu thiết kế tự ngăn cản bạn xử lý nó - nếu kết nối được làm lại, singleton có thể được khởi tạo một lần nữa, như không có trường hợp nào tồn tại vào thời điểm đó.

Nói cách khác, bạn có thể tạo các tình huống trong đó hợp lý cho người đơn có IDisposable.

+1

Sự thật là, nếu bạn có thể hình dung được lý do cho đối tượng của bạn được thay thế trong suốt thời gian của ứng dụng, đối tượng đó có thể không hợp pháp là một singleton. Còn về lý do ... Nói tôi tóm lấy Singleton và đưa nó cho những thứ cần thiết (nguyên nhân, ya biết, tiêm phụ thuộc là một điều tốt). Sau đó, một cái gì đó ở đâu đó quyết định thay thế nó. Bây giờ có * hai đơn * (cái cũ tôi đã đi qua, và cái mới). Khả năng này rất vi phạm toàn bộ định nghĩa của Singleton - tức là, mã đó luôn thấy chính xác một ví dụ. – cHao

+0

Vì vậy, trước hết, câu trả lời cũ của câu truyện cổ tích Batman. Thứ hai, ví dụ của bạn có vẻ không hợp lý.Nếu bạn truyền một thể hiện của Singleton cho một đối tượng và sau đó một số đối tượng khác quyết định nó muốn Singleton, thì mẫu đó cho phép điều đó - nó sẽ lấy một tham chiếu về đối tượng hiện có trong bộ nhớ. Điểm tôi đã làm là mô hình không ngăn cản bạn xử lý vật thể. Trong trường hợp đó, sử dụng ví dụ của bạn, nếu đối tượng đã được xử lý thì cá thể đơn sẽ không còn tồn tại, vì vậy khi một đối tượng khác yêu cầu Singleton, nó sẽ nhận được một cá thể mới. –

+2

Nếu đối tượng được xử lý, thể hiện * vẫn tồn tại - bất kỳ ai đã mua nó trước khi nó được xử lý vẫn sẽ có nó. Vì vậy, nó rõ ràng không thể được GCed, nhưng nó bây giờ không sử dụng được. Hãy suy nghĩ về những hậu quả của tuyên bố đó trong một bối cảnh đa luồng. Một biểu thức như 'Singleton.getInstance(). DoStuff()' không còn là luồng an toàn nữa, ngay cả khi cả hai 'getInstance' và' doStuff' đều là *. Một luồng có thể đi vào giữa hai cuộc gọi và vứt bỏ cá thể mà chúng ta vừa mua. Và điều đó thậm chí còn giả định kịch bản của mọi người luôn luôn gọi là 'getInstance' của bạn. – cHao

3

Miễn là finalizer của bạn không gọi phương thức (chẳng hạn như Dispose) trên bất kỳ đối tượng được quản lý nào khác, bạn sẽ ổn. Chỉ cần nhớ rằng trật tự quyết toán không phải là xác định. Tức là, nếu đối tượng đơn lẻ của bạn Foo có tham chiếu đến đối tượng Bar yêu cầu xử lý, bạn không thể viết một cách đáng tin cậy:

~Foo() 
{ 
    Bar.Dispose(); 
} 

Bộ thu gom rác có thể đã thu thập được Bar.

Khi có nguy cơ bước vào một đống OO goo (tức là bắt đầu một cuộc chiến), một cách thay thế để sử dụng singleton là sử dụng lớp tĩnh.

+0

+1 cho lớp tĩnh - đơn không cần thiết vì tính phổ biến của chúng sẽ đề xuất. –

+0

Khái niệm sự khác biệt duy nhất giữa một singleton và một lớp tĩnh là một singleton là một trường tĩnh trỏ vào nhiều trường thể hiện, trong đó một lớp tĩnh là một loạt các trường tĩnh. Khi nói đến quản lý tài nguyên, sự khác biệt giữa 2 là không đáng kể. –

+0

những gì scott nói - singletons dùng một lần chỉ là một vấn đề cơ bản – annakata

2

Mặc dù nó có thể giúp bạn xem xét mã xem xét và cảnh báo FxCop, không có gì sai về mặt bản chất khi triển khai trình hoàn tất mà không có IDisposable. Tuy nhiên, làm như vậy trên một singleton không phải là một cách đáng tin cậy để nắm bắt quá trình hoặc AppDomain tear-down.

Có nguy cơ đưa ra lời khuyên thiết kế chủ quan: Nếu đối tượng thực sự là không trạng thái, hãy biến nó thành lớp tĩnh. Nếu nó là stateful, câu hỏi tại sao nó là một Singleton: bạn đang tạo ra một biến toàn cầu có thể thay đổi. Nếu bạn đang cố gắng nắm bắt ứng dụng gần, hãy xử lý nó khi vòng lặp chính của bạn thoát.

+0

"Nếu đối tượng thực sự là phi trạng thái, hãy biến nó thành một lớp tĩnh. Nếu nó là trạng thái, đặt câu hỏi tại sao nó là Singleton", tôi hoàn toàn đồng ý, nhưng tôi thích sự trích dẫn. Có một tham chiếu cho điều đó? – annakata

+0

Tôi đã thực hiện điều đó một cách nhanh chóng. Tôi đã được lan can chống lại hầu hết việc sử dụng Singletons một thời gian bây giờ trên Portland Pattern Repository do ảnh hưởng của họ đối với quan hệ nhân quả trong mã. –

0

Khả năng ứng dụng Singleton cho bất kỳ tình huống đặc biệt sang một bên,

tôi nghĩ rằng không có gì sai với Vứt bỏ S là ingleton. Kết hợp với sự khởi tạo lười biếng nó chỉ có nghĩa là bạn giải phóng tài nguyên nếu bạn không cần nó tạm thời, và sau đó lấy lại nó khi cần thiết.

+0

Đó là một đoạn mã có thể có được và "Vứt bỏ" một singleton không nói gì về việc liệu có ai khác có thể vẫn đang sử dụng nó hay không. Nếu finalizer được gọi, không ai sử dụng nó; lưu ý rằng cách duy nhất có khả năng xảy ra sẽ là nếu tham chiếu tĩnh/toàn cục tới singleton là một WeakReference. Nếu không, tham chiếu đó sẽ tự bảo vệ singleton khỏi việc hoàn thiện. – supercat

0

Nếu bạn muốn tạo một singleton với trình hoàn thiện, bạn có thể có tham chiếu tĩnh là một tham chiếu WeakReference. Điều này sẽ yêu cầu thêm một chút công việc để đảm bảo an toàn luồng trong accessor, nhưng nó sẽ cho phép singleton được thu gom rác khi không ai sử dụng nó (nếu ai đó gọi phương thức GetInstance(), họ sẽ nhận được trường hợp mới). Nếu một tham chiếu tĩnh tĩnh được sử dụng, nó sẽ giữ cho cá thể singleton còn sống ngay cả khi không có tham chiếu nào khác đến nó.

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