it-swarm-vi.com

Vậy Singletons là xấu, thì sao?

Gần đây đã có rất nhiều cuộc thảo luận về các vấn đề với việc sử dụng (và lạm dụng) Singletons. Tôi cũng từng là một trong những người đó trong sự nghiệp của mình. Tôi có thể thấy vấn đề hiện tại là gì, tuy nhiên, vẫn còn nhiều trường hợp tôi không thể thấy một sự thay thế Nice - và không có nhiều cuộc thảo luận chống Singleton thực sự cung cấp một vấn đề.

Đây là một ví dụ thực tế từ một dự án lớn gần đây tôi đã tham gia:

Ứng dụng này là một máy khách dày với nhiều màn hình và thành phần riêng biệt sử dụng lượng dữ liệu khổng lồ từ trạng thái máy chủ không được cập nhật quá thường xuyên. Dữ liệu này về cơ bản được lưu trữ trong một đối tượng "người quản lý" Singleton - "trạng thái toàn cầu" đáng sợ. Ý tưởng là có một vị trí này trong ứng dụng giữ dữ liệu được lưu trữ và đồng bộ hóa, và sau đó bất kỳ màn hình mới nào được mở có thể chỉ cần truy vấn hầu hết những gì họ cần từ đó, mà không yêu cầu lặp lại các dữ liệu hỗ trợ khác nhau từ máy chủ. Liên tục yêu cầu máy chủ sẽ mất quá nhiều băng thông - và tôi đang nói thêm hàng ngàn đô la hóa đơn Internet mỗi tuần, vì vậy điều đó không thể chấp nhận được.

Có cách tiếp cận nào khác có thể phù hợp ở đây hơn là về cơ bản có loại đối tượng bộ đệm bộ quản lý dữ liệu toàn cầu này không? Tất nhiên, đối tượng này không phải là một "Singleton", nhưng về mặt khái niệm nó có ý nghĩa là một. Một thay thế sạch đẹp ở đây là gì?

570
Bobby Tables

Điều quan trọng là phải phân biệt ở đây giữa trường hợp đơn lẻmẫu thiết kế Singleton .

Các trường hợp đơn lẻ chỉ đơn giản là một thực tế. Hầu hết các ứng dụng chỉ được thiết kế để hoạt động với một cấu hình tại một thời điểm, một giao diện người dùng tại một thời điểm, một hệ thống tệp tại một thời điểm, v.v. Nếu có rất nhiều trạng thái hoặc dữ liệu được duy trì, thì chắc chắn bạn sẽ muốn có một ví dụ và giữ cho nó tồn tại càng lâu càng tốt.

Singleton mẫu thiết kế là một mẫu rất cụ thể type của một trường hợp duy nhất, cụ thể là một ví dụ:

  • Có thể truy cập thông qua một trường toàn cầu, tĩnh;
  • Được tạo hoặc khi khởi tạo chương trình hoặc khi truy cập lần đầu;
  • Không có nhà xây dựng công cộng (không thể khởi tạo trực tiếp);
  • Không bao giờ được giải phóng rõ ràng (hoàn toàn giải phóng khi chấm dứt chương trình).

Chính vì sự lựa chọn thiết kế cụ thể này mà mẫu này đưa ra một số vấn đề dài hạn tiềm ẩn:

  • Không có khả năng sử dụng các lớp trừu tượng hoặc giao diện;
  • Không có khả năng phân lớp;
  • Khớp nối cao trên ứng dụng (khó sửa đổi);
  • Khó kiểm tra (không thể giả/giả trong các bài kiểm tra đơn vị);
  • Khó song song trong trường hợp trạng thái đột biến (yêu cầu khóa mở rộng);
  • và như thế.

Không có triệu chứng nào trong số này thực sự là đặc hữu của các trường hợp đơn lẻ, chỉ là mẫu Singleton.

Bạn có thể làm gì thay thế? Đơn giản là đừng sử dụng mẫu Singleton.

Trích dẫn từ câu hỏi:

Ý tưởng là có một vị trí này trong ứng dụng giữ dữ liệu được lưu trữ và đồng bộ hóa, và sau đó bất kỳ màn hình mới nào được mở có thể chỉ cần truy vấn hầu hết những gì họ cần từ đó, mà không yêu cầu lặp lại các dữ liệu hỗ trợ khác nhau từ máy chủ. Liên tục yêu cầu máy chủ sẽ mất quá nhiều băng thông - và tôi đang nói thêm hàng ngàn đô la hóa đơn Internet mỗi tuần, vì vậy điều đó không thể chấp nhận được.

Khái niệm này có một cái tên, như bạn sắp xếp gợi ý nhưng âm thanh không chắc chắn. Nó được gọi là bộ đệm . Nếu bạn muốn nhận được sự ưa thích, bạn có thể gọi nó là "bộ đệm ngoại tuyến" hoặc chỉ là một bản sao ngoại tuyến của dữ liệu từ xa.

Một bộ đệm không cần phải là một singleton. Nó may cần phải là một phiên bản duy nhất nếu bạn muốn tránh tìm nạp cùng một dữ liệu cho nhiều trường hợp bộ đệm; nhưng điều đó không có nghĩa là bạn thực sự phải phơi bày mọi thứ cho mọi người.

Điều đầu tiên tôi làm là tách riêng các khu vực chức năng của bộ đệm thành các giao diện riêng biệt. Ví dụ: giả sử bạn đang tạo bản sao YouTube tồi tệ nhất thế giới dựa trên Microsoft Access:

[.__.] MSAccessCache [.__.] ▲ [.__.] | [.__.] + ----------------- + -------- --------- + [.___.] | | | [.__.] IMediaCache IProfileCache IPageCache [.__.] | | | [.__.] | | | [.___.] VideoPage MyAccountPage MostP phổ biến [.__.]

Tại đây, bạn có một số giao diện mô tả cụ thể loại dữ liệu mà một lớp cụ thể có thể cần truy cập vào - phương tiện, hồ sơ người dùng và trang tĩnh (như trang trước ). Tất cả điều đó là được triển khai bởi một mega-cache, nhưng bạn thiết kế các lớp riêng lẻ của mình để chấp nhận các giao diện thay vào đó, vì vậy chúng không quan tâm chúng có loại cá thể nào. Bạn khởi tạo thể hiện vật lý một lần, khi chương trình của bạn bắt đầu, và sau đó chỉ bắt đầu chuyển xung quanh các thể hiện (chuyển sang một loại giao diện cụ thể) thông qua các hàm tạo và thuộc tính công cộng.

Điều này được gọi là Dependency Injection , nhân tiện; bạn không cần sử dụng Spring hoặc bất kỳ bộ chứa IoC đặc biệt nào, miễn là thiết kế lớp chung của bạn chấp nhận sự phụ thuộc của nó từ người gọi thay vì tự khởi tạo chúng hoặc tham chiếu trạng thái toàn cầu.

Tại sao bạn nên sử dụng thiết kế dựa trên giao diện? Ba lý do:

  1. Nó làm cho mã dễ đọc hơn; bạn có thể hiểu rõ ràng từ các giao diện chính xác dữ liệu nào các lớp phụ thuộc phụ thuộc vào.

  2. Nếu và khi bạn nhận ra rằng Microsoft Access không phải là lựa chọn tốt nhất cho back-end dữ liệu, bạn có thể thay thế nó bằng thứ gì đó tốt hơn - giả sử SQL Server.

  3. Nếu và khi bạn nhận ra rằng SQL Server không phải là lựa chọn tốt nhất cho phương tiện cụ thể, bạn có thể chia nhỏ việc triển khai của mình mà không ảnh hưởng đến bất kỳ phần nào khác của hệ thống . Đó là nơi sức mạnh thực sự của sự trừu tượng đến.

Nếu bạn muốn tiến thêm một bước thì bạn có thể sử dụng bộ chứa IoC (khung DI) như Spring (Java) hoặc Unity (.NET). Hầu như mọi khung DI sẽ thực hiện quản lý trọn đời của riêng nó và đặc biệt cho phép bạn xác định một dịch vụ cụ thể là một thể hiện duy nhất (thường gọi nó là "singleton", nhưng Điều đó chỉ dành cho sự quen thuộc). Về cơ bản các khung này giúp bạn tiết kiệm phần lớn công việc của khỉ khi chuyển thủ công xung quanh các trường hợp, nhưng chúng không thực sự cần thiết. Bạn không cần bất kỳ công cụ đặc biệt nào để thực hiện thiết kế này.

Để hoàn thiện, tôi nên chỉ ra rằng thiết kế ở trên thực sự không lý tưởng. Khi bạn đang xử lý bộ đệm (như hiện tại), bạn thực sự nên có một layer. Nói cách khác, một thiết kế như thế này:

[.__.] + - IMediaRep repository [.__.] | [.__.] Cache (Chung) --------------- + - IProfileRep repository [.__.] | [.__.] | + - IPageRep repository [.__.] + ----------------- + ----------------- + [.__. |] | | | [.__.] IMediaCache IProfileCache IPageCache [.__.] | | | [.__.] | | | [.___.] VideoPage MyAccountPage MostP phổ biến [.__.]

Lợi ích của việc này là bạn thậm chí không cần phải chia nhỏ thể hiện Cache nếu bạn quyết định cấu trúc lại; bạn có thể thay đổi cách Media được lưu trữ đơn giản bằng cách cung cấp cho nó cách triển khai thay thế IMediaRepository. Nếu bạn nghĩ về việc làm thế nào điều này khớp với nhau, bạn sẽ thấy rằng nó vẫn chỉ tạo ra một thể hiện vật lý của bộ đệm, vì vậy bạn không bao giờ cần phải tìm nạp cùng một dữ liệu hai lần.

Không có gì trong số này để nói rằng mọi phần mềm duy nhất trên thế giới cần phải được kiến ​​trúc theo các tiêu chuẩn chính xác này về sự gắn kết cao và khớp nối lỏng lẻo; nó phụ thuộc vào quy mô và phạm vi của dự án, nhóm của bạn, ngân sách của bạn, thời hạn, v.v. Nhưng nếu bạn đang hỏi thiết kế tốt nhất là gì (để sử dụng thay cho một singleton), thì đây là nó.

P.S. Như những người khác đã tuyên bố, có lẽ không phải ý tưởng tốt nhất cho các lớp phụ thuộc nhận thức được rằng họ đang sử dụng a cache - đó là một chi tiết triển khai mà họ không bao giờ nên quan tâm. Điều đó đang được nói, kiến ​​trúc tổng thể vẫn sẽ trông rất giống với những gì trong hình trên, bạn sẽ không đề cập đến các giao diện riêng lẻ như Caches. Thay vào đó, bạn đặt tên cho chúng Dịch vụ hoặc một cái gì đó tương tự.

826
Aaronaught

Trong trường hợp bạn đưa ra, có vẻ như việc sử dụng a Singleton không phải là vấn đề, nhưng là triệu chứng của một vấn đề - một vấn đề kiến ​​trúc lớn hơn.

Tại sao các màn hình truy vấn đối tượng bộ đệm cho dữ liệu? Bộ nhớ đệm phải được minh bạch cho khách hàng. Cần có một sự trừu tượng hóa thích hợp để cung cấp dữ liệu và việc thực hiện sự trừu tượng hóa đó có thể sử dụng bộ nhớ đệm.

Vấn đề có thể là sự phụ thuộc giữa các bộ phận của hệ thống không được thiết lập chính xác và điều này có thể mang tính hệ thống.

Tại sao các màn hình cần phải có kiến ​​thức về nơi họ lấy dữ liệu của họ? Tại sao các màn hình không được cung cấp với một đối tượng có thể đáp ứng yêu cầu dữ liệu của họ (đằng sau đó bộ nhớ cache bị ẩn)? Thông thường, trách nhiệm tạo màn hình không tập trung, và do đó không có quan điểm rõ ràng về việc tiêm phụ thuộc.

Một lần nữa, chúng tôi đang xem xét các vấn đề kiến ​​trúc và thiết kế quy mô lớn.

Ngoài ra, nó là rất quan trọng để hiểu rằng trọn đời của một đối tượng có thể được tách hoàn toàn khỏi cách tìm thấy đối tượng để sử dụng.

Bộ đệm sẽ phải tồn tại trong suốt vòng đời của ứng dụng (có ích), vì vậy thời gian tồn tại của đối tượng là của Singleton.

Nhưng vấn đề với Singleton (ít nhất là việc triển khai chung Singleton như một lớp/thuộc tính tĩnh), là cách các lớp khác sử dụng nó đi về việc tìm kiếm nó.

Với việc triển khai Singleton tĩnh, quy ước chỉ đơn giản là sử dụng nó bất cứ khi nào cần thiết. Nhưng điều đó hoàn toàn che giấu sự phụ thuộc và kết hợp chặt chẽ giữa hai lớp.

Nếu chúng tôi cung cấp sự phụ thuộc vào lớp, thì sự phụ thuộc đó là rõ ràng và tất cả các lớp tiêu thụ cần phải có kiến ​​thức về hợp đồng có sẵn để sử dụng.

48
quentin-starin

Tôi đã viết một cả chương chỉ cho câu hỏi này. Chủ yếu là trong bối cảnh của các trò chơi, nhưng hầu hết nên áp dụng bên ngoài các trò chơi.

tl; dr:

Mẫu Gang of Four Singleton thực hiện hai điều: cung cấp cho bạn quyền truy cập thuận tiện vào một đối tượng từ bất cứ đâu và đảm bảo rằng chỉ có một phiên bản của nó có thể được tạo. 99% thời gian, tất cả những gì bạn quan tâm là nửa đầu của điều đó, và kéo theo nửa sau để có thêm giới hạn không cần thiết.

Không chỉ vậy, nhưng có những giải pháp tốt hơn để cung cấp truy cập thuận tiện. Làm cho một đối tượng toàn cầu là tùy chọn hạt nhân để giải quyết điều đó, và làm cho nó dễ dàng để phá hủy sự đóng gói của bạn. Tất cả mọi thứ xấu về toàn cầu đều áp dụng hoàn toàn cho singletons.

Nếu bạn đang sử dụng nó chỉ vì bạn có nhiều vị trí trong mã cần chạm vào cùng một đối tượng, hãy thử tìm một cách tốt hơn để cung cấp cho nó chỉ những đối tượng đó mà không để lộ ra toàn bộ cơ sở mã. Các giải pháp khác:

  • Bỏ hoàn toàn. Tôi đã thấy rất nhiều lớp học đơn lẻ không có bất kỳ trạng thái nào và chỉ là túi chức năng của trình trợ giúp. Những người không cần một ví dụ nào cả. Chỉ cần biến chúng thành các hàm tĩnh hoặc di chuyển chúng vào một trong các lớp mà hàm lấy làm đối số. Bạn sẽ không cần một lớp Math đặc biệt nếu bạn chỉ có thể làm 123.Abs().

  • Truyền nó xung quanh. Giải pháp đơn giản nếu một phương thức cần một số đối tượng khác là chỉ truyền nó vào. Không có gì sai khi truyền một số đối tượng xung quanh.

  • Đặt nó vào lớp cơ sở. Nếu bạn có rất nhiều lớp cần truy cập vào một số đối tượng đặc biệt và chúng có chung một lớp cơ sở, bạn có thể làm cho đối tượng đó trở thành một thành viên trên cơ sở Khi bạn xây dựng nó, vượt qua trong đối tượng. Bây giờ tất cả các đối tượng dẫn xuất có thể có được nó khi họ cần. Nếu bạn làm cho nó được bảo vệ, bạn đảm bảo đối tượng vẫn được đóng gói.

45
munificent

Nó không phải là nhà nước toàn cầu, đó là vấn đề.

Thực sự bạn chỉ cần lo lắng về global mutable state. Trạng thái không đổi không bị ảnh hưởng bởi các tác động phụ và do đó ít xảy ra vấn đề hơn.

Mối quan tâm chính với singleton là nó thêm khớp nối và do đó làm cho mọi thứ như kiểm tra khó khăn (er). Bạn có thể giảm khớp nối bằng cách lấy singleton từ một nguồn khác (ví dụ: nhà máy). Điều này sẽ cho phép bạn tách mã từ một trường hợp cụ thể (mặc dù bạn trở nên gắn kết hơn với nhà máy (nhưng ít nhất nhà máy có thể có các triển khai thay thế cho các giai đoạn khác nhau)).

Trong tình huống của bạn, tôi nghĩ rằng bạn có thể thoát khỏi nó miễn là người độc thân của bạn thực sự thực hiện một giao diện (để có thể sử dụng một giải pháp thay thế trong các tình huống khác).

Nhưng một lợi thế lớn khác với singletons là một khi chúng được đặt tại chỗ để loại bỏ chúng khỏi mã và thay thế chúng bằng một thứ khác sẽ trở thành một nhiệm vụ khó khăn thực sự (lại có khớp nối đó).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.
21
Martin York

Rồi sao? Vì không ai nói điều đó: Hộp công cụ. Đó là nếu bạn muốn biến toàn cục.

Có thể tránh lạm dụng Singleton bằng cách xem xét vấn đề từ một góc độ khác. Giả sử một ứng dụng chỉ cần một phiên bản của một lớp và ứng dụng sẽ cấu hình lớp đó khi khởi động: Tại sao chính lớp đó phải chịu trách nhiệm cho việc trở thành một người độc thân? Có vẻ khá logic cho ứng dụng đảm nhận trách nhiệm này, vì ứng dụng yêu cầu loại hành vi này. Ứng dụng, không phải là thành phần, nên là đơn. Sau đó, ứng dụng tạo một phiên bản của thành phần có sẵn cho bất kỳ mã cụ thể nào của ứng dụng sẽ sử dụng. Khi một ứng dụng sử dụng một số thành phần như vậy, nó có thể tổng hợp chúng thành cái mà chúng ta gọi là hộp công cụ.

Nói một cách đơn giản, hộp công cụ của ứng dụng là một singleton chịu trách nhiệm tự cấu hình hoặc cho phép cơ chế khởi động của ứng dụng định cấu hình nó ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Nhưng đoán xem? Nó là một người độc thân!

Và một singleton là gì?

Có lẽ đó là nơi sự nhầm lẫn bắt đầu.

Đối với tôi, singleton là một đối tượng được thi hành để chỉ có một thể hiện duy nhất và luôn luôn. Bạn có thể truy cập nó ở bất cứ đâu, bất cứ lúc nào mà không cần phải khởi tạo nó. Đó là lý do tại sao nó liên quan chặt chẽ đến static . Để so sánh, static về cơ bản là giống nhau, ngoại trừ đó không phải là một ví dụ. Chúng tôi không cần phải khởi tạo nó, thậm chí chúng tôi không thể, vì nó được phân bổ tự động. Và điều đó có thể và không mang lại vấn đề.

Từ kinh nghiệm của tôi, chỉ cần thay thế static cho Singleton đã giải quyết được nhiều vấn đề trong dự án túi chắp vá cỡ trung bình mà tôi đang thực hiện. Điều đó chỉ có nghĩa là nó có một số cách sử dụng cho các dự án được thiết kế xấu. Tôi nghĩ rằng có quá nhiều thảo luận nếu mẫu đơnhữu ích hay không và tôi thực sự không thể tranh luận nếu nó thực sự xấ . Nhưng vẫn còn nói chung là có lợi cho singleton so với các phương thức tĩnh, nói chung .

Điều duy nhất tôi chắc chắn là xấu về singletons, là khi chúng ta sử dụng chúng trong khi bỏ qua các thực hành tốt. Đó thực sự là một cái gì đó không dễ dàng để đối phó. Nhưng thực hành xấu có thể được áp dụng cho bất kỳ mô hình. Và, tôi biết, thật quá chung chung để nói rằng ... Ý tôi là có quá nhiều thứ.

Đừng hiểu lầm tôi!

Nói một cách đơn giản, giống như vars toàn cầu , singletons nên still = được tránh mọi lúc . Đặc biệt bởi vì họ bị lạm dụng quá mức. Nhưng vars toàn cầu không thể luôn luôn tránh và chúng ta nên sử dụng chúng trong trường hợp cuối cùng đó.

Dù sao, có nhiều đề xuất khác ngoài Hộp công cụ, và giống như hộp công cụ, mỗi người đều có ứng dụng của mình ...

Các lựa chọn thay thế khác

  • bài viết hay nhất tôi vừa đọc về singletons gợi ý Trình định vị dịch vụ như một cách thay thế. Đối với tôi về cơ bản đó là " Hộp công cụ tĩnh ", nếu bạn muốn. Nói cách khác, biến Bộ định vị dịch vụ thành Singleton và bạn có Hộp công cụ. Tất nhiên, điều đó đi ngược lại với đề xuất ban đầu là tránh singleton, nhưng điều đó chỉ để thực thi vấn đề của singleton là cách nó được sử dụng chứ không phải bản thân mô hình.

  • Khác đề xuất Kiểu nhà máy như một cách thay thế. Đó là sự thay thế đầu tiên tôi nghe được từ một đồng nghiệp và chúng tôi đã nhanh chóng loại bỏ nó để sử dụng như var toàn cầu. Nó chắc chắn có cách sử dụng của nó, nhưng singletons cũng vậy.

Cả hai lựa chọn thay thế ở trên là những lựa chọn tốt. Nhưng tất cả phụ thuộc vào cách sử dụng của bạn.

Bây giờ, việc tránh những người độc thân nên tránh bằng mọi giá chỉ là sai ...

  • Aaronaught 'câu trả lời gợi ý cho không bao giờ sử dụng singletons , vì một loạt lý do . Nhưng tất cả đều là lý do chống lại việc nó bị sử dụng và lạm dụng như thế nào, không trực tiếp chống lại mô hình đó. Tôi đồng ý với tất cả những lo lắng về những điểm đó, làm thế nào tôi có thể? Tôi chỉ nghĩ rằng nó sai lệch.

Không có khả năng (để trừu tượng hoặc lớp con) thực sự là có, nhưng vì vậy những gì? Nó không có nghĩa cho điều đó. Không có không có khả năng giao diện, theo như tôi có thể nói . Khớp nối cao cũng có thể ở đó, nhưng đó chỉ là vì cách nó thường được sử dụng. Nó không phải . Trong thực tế, bản thân khớp nối không liên quan gì đến mẫu singleton. Điều đó đã được làm rõ, nó cũng đã loại bỏ những khó khăn để kiểm tra. Đối với khó khăn để song song, điều đó phụ thuộc vào ngôn ngữ và nền tảng, do đó, một lần nữa, không phải là một vấn đề trên mẫu.

Ví dụ thực tế

Tôi thường thấy 2 được sử dụng, cả ủng hộ và chống lại những người độc thân. Bộ đệm web (trường hợp của tôi) và dịch vụ nhật ký .

Việc ghi nhật ký, một số người sẽ tranh luận , là một ví dụ đơn lẻ hoàn hảo, bởi vì, và tôi trích dẫn:

  • Người yêu cầu cần một đối tượng nổi tiếng để gửi yêu cầu đăng nhập. Điều này có nghĩa là một điểm truy cập toàn cầu.
  • Vì dịch vụ ghi nhật ký là một nguồn sự kiện duy nhất mà nhiều người nghe có thể đăng ký, nên chỉ cần có một phiên bản.
  • Mặc dù các ứng dụng khác nhau có thể đăng nhập vào các thiết bị đầu ra khác nhau, cách chúng đăng ký trình nghe của chúng luôn giống nhau. Tất cả các tùy chỉnh được thực hiện thông qua người nghe. Khách hàng có thể yêu cầu đăng nhập mà không biết văn bản sẽ được ghi lại như thế nào hoặc ở đâu. Do đó, mọi ứng dụng sẽ sử dụng dịch vụ ghi nhật ký theo cùng một cách.
  • Bất kỳ ứng dụng nào cũng có thể thoát khỏi chỉ với một phiên bản của dịch vụ ghi nhật ký.
  • Bất kỳ đối tượng nào cũng có thể là người yêu cầu đăng nhập, bao gồm các thành phần có thể tái sử dụng, do đó chúng không nên được ghép nối với bất kỳ ứng dụng cụ thể nào.

Mặc dù những người khác sẽ lập luận rằng việc mở rộng dịch vụ nhật ký trở nên khó khăn khi bạn cuối cùng nhận ra rằng nó thực sự không nên chỉ là một trường hợp.

Vâng, tôi nói cả hai đối số là hợp lệ. Vấn đề ở đây, một lần nữa, không nằm ở mẫu đơn. Đó là quyết định kiến ​​trúc và trọng số nếu tái cấu trúc là một rủi ro khả thi. Đó là một vấn đề nữa khi, thông thường, tái cấu trúc là biện pháp khắc phục cần thiết cuối cùng.

20
cregox

Vấn đề chính của tôi với mẫu thiết kế singleton là rất khó để viết các bài kiểm tra đơn vị tốt cho ứng dụng của bạn.

Mọi thành phần có sự phụ thuộc vào "trình quản lý" này sẽ làm như vậy bằng cách truy vấn thể hiện đơn lẻ của nó. Và nếu bạn muốn viết một bài kiểm tra đơn vị cho một thành phần như vậy, bạn phải đưa dữ liệu vào trường hợp đơn lẻ này, điều này có thể không dễ dàng.

Mặt khác, "trình quản lý" của bạn được đưa vào các thành phần phụ thuộc thông qua tham số hàm tạo và thành phần đó không biết loại cụ thể của trình quản lý, chỉ một giao diện hoặc lớp cơ sở trừu tượng mà trình quản lý thực hiện, sau đó là một đơn vị kiểm tra có thể cung cấp các triển khai thay thế của người quản lý khi kiểm tra các phụ thuộc.

Nếu bạn sử dụng IOC container để định cấu hình và khởi tạo các thành phần tạo nên ứng dụng của bạn, thì bạn có thể dễ dàng định cấu hình bộ chứa IOC để chỉ tạo một phiên bản của "người quản lý", cho phép bạn đạt được điều tương tự, chỉ một trường hợp kiểm soát bộ đệm ứng dụng toàn cầu.

Nhưng nếu bạn không quan tâm đến các bài kiểm tra đơn vị, thì một mẫu thiết kế đơn có thể hoàn toàn ổn. (nhưng dù sao tôi cũng sẽ không làm điều đó)

5
Pete

Một singleton không theo cách cơ bản xấ, theo nghĩa là bất kỳ thứ gì tính toán thiết kế đều có thể tốt hoặc xấu. Nó chỉ có thể đúng (cho kết quả mong đợi) hoặc không. Nó cũng có thể hữu ích hoặc không, nếu nó làm cho mã rõ ràng hơn hoặc hiệu quả hơn.

Một trường hợp trong đó singletons hữu ích là khi chúng đại diện cho một thực thể thực sự là duy nhất. Trong hầu hết các môi trường, cơ sở dữ liệu là duy nhất, thực sự chỉ có một cơ sở dữ liệu. Kết nối với cơ sở dữ liệu đó có thể phức tạp vì nó yêu cầu quyền đặc biệt hoặc duyệt qua một số loại kết nối. Tổ chức kết nối đó vào một singleton có lẽ rất có ý nghĩa cho lý do này một mình.

Nhưng bạn cũng cần chắc chắn rằng singleton thực sự là một singleton chứ không phải là biến toàn cục. Điều này quan trọng khi cơ sở dữ liệu duy nhất, duy nhất thực sự là 4 cơ sở dữ liệu, mỗi cơ sở cho các sản phẩm, dàn dựng, phát triển và thử nghiệm. Cơ sở dữ liệu Singleton sẽ tìm ra cái nào sẽ được kết nối với, lấy ví dụ duy nhất cho cơ sở dữ liệu đó, kết nối nó nếu cần và trả lại cho người gọi.

Khi một người độc thân không thực sự là một người độc thân (đây là lúc hầu hết các lập trình viên buồn bã), đó là một thế giới tức thời uể oải, không có cơ hội để đưa ra một ví dụ chính xác.

Một tính năng hữu ích khác của một mẫu singleton được thiết kế tốt là nó thường không thể quan sát được. Người gọi yêu cầu kết nối. Dịch vụ cung cấp nó có thể trả về một đối tượng được gộp hoặc nếu nó đang thực hiện kiểm tra, nó có thể tạo một đối tượng mới cho mỗi người gọi hoặc thay vào đó cung cấp một đối tượng giả.

4

Việc sử dụng mẫu singleton đại diện cho các đối tượng thực tế là hoàn toàn chấp nhận được. Tôi viết cho iPhone và có rất nhiều singletons trong khung Cacao Touch. Bản thân ứng dụng được đại diện bởi một đơn vị của lớp UIApplication. Bạn chỉ có một ứng dụng, vì vậy nó phù hợp để đại diện cho ứng dụng đó.

Sử dụng một singleton như một lớp trình quản lý dữ liệu là được miễn là nó được thiết kế đúng. Nếu đó là một nhóm các thuộc tính dữ liệu, điều đó không tốt hơn phạm vi toàn cầu. Nếu đó là một tập hợp các getters và setters, điều đó tốt hơn, nhưng vẫn không tuyệt vời. Nếu đó là một lớp thực sự quản lý tất cả giao diện với dữ liệu, bao gồm cả việc tìm nạp dữ liệu từ xa, bộ nhớ đệm, thiết lập và phân tích ... Điều đó có thể rất hữu ích.

3
Dan Ray

Singletons chỉ là hình chiếu của một kiến ​​trúc hướng dịch vụ vào một chương trình.

API là một ví dụ về singleton ở cấp độ giao thức. Bạn truy cập Twitter, Google, vv thông qua những gì cơ bản là singletons. Vậy tại sao singletons trở nên tồi tệ trong một chương trình?

Nó phụ thuộc vào cách bạn nghĩ về một chương trình. Nếu bạn nghĩ về một chương trình như một xã hội của các dịch vụ thay vì các trường hợp được lưu trong bộ nhớ cache ngẫu nhiên thì singletons có ý nghĩa hoàn hảo.

Singletons là một điểm truy cập dịch vụ. Giao diện công cộng cho một thư viện chức năng ràng buộc chặt chẽ có lẽ ẩn chứa một kiến ​​trúc nội bộ rất tinh vi.

Vì vậy, tôi không thấy một singleton khác với một nhà máy. Ví dụ, singleton có thể có các tham số hàm tạo. Nó có thể được tạo bởi một số ngữ cảnh biết cách giải quyết máy in mặc định đối với tất cả các cơ chế lựa chọn có thể, ví dụ. Để thử nghiệm, bạn có thể chèn giả của riêng bạn. Vì vậy, nó có thể khá linh hoạt.

Khóa này nằm trong một chương trình khi tôi thực thi và cần một chút chức năng, tôi có thể truy cập vào singleton với sự tự tin hoàn toàn rằng dịch vụ đã sẵn sàng và sẵn sàng để sử dụng. Đây là chìa khóa khi có các luồng khác nhau bắt đầu trong một quy trình phải đi qua một máy trạng thái để được coi là sẵn sàng.

Thông thường tôi sẽ bọc một lớp XxxService bao bọc một singleton quanh lớp Xxx. Người độc thân không ở trong lớp Xxx, nó được tách ra thành một lớp khác, XxxService. Điều này là do Xxx có thể có nhiều phiên bản, mặc dù điều đó không có khả năng, nhưng chúng tôi vẫn muốn có một thể hiện Xxx có thể truy cập trên toàn cầu trên mỗi hệ thống. XxxService cung cấp một mối quan tâm tốt đẹp. Xxx không phải thực thi chính sách đơn lẻ, tuy nhiên chúng tôi có thể sử dụng Xxx như một đơn vị khi chúng tôi cần.

Cái gì đó như:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  
3
Todd Hoff

Có mỗi màn hình đưa Trình quản lý vào hàm tạo của chúng.

Khi bạn khởi động ứng dụng của mình, bạn tạo một phiên bản của trình quản lý và chuyển nó đi xung quanh.

Điều này được gọi là Inversion of Control và cho phép bạn trao đổi bộ điều khiển khi cấu hình thay đổi và trong các thử nghiệm. Ngoài ra, bạn có thể chạy một số phiên bản của ứng dụng hoặc các phần của ứng dụng của bạn song song (tốt cho việc thử nghiệm!). Cuối cùng, người quản lý của bạn sẽ chết với đối tượng sở hữu của nó (lớp khởi động).

Vì vậy, cấu trúc ứng dụng của bạn giống như một cái cây, nơi những thứ ở trên sở hữu mọi thứ được sử dụng bên dưới chúng. Đừng triển khai một ứng dụng như lưới, nơi mọi người biết mọi người và tìm thấy nhau thông qua các phương pháp toàn cầu.

1
Alexander Torstling

IMO, ví dụ của bạn nghe ổn. Tôi đề nghị bao thanh toán như sau: đối tượng bộ đệm cho từng đối tượng dữ liệu (và đằng sau mỗi); các đối tượng bộ đệm và các đối tượng truy cập db có cùng giao diện. Điều này cho phép khả năng trao đổi bộ nhớ cache trong và ngoài mã; cộng với nó cho một lộ trình mở rộng dễ dàng.

Đồ họa:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

Bộ truy cập DB và bộ đệm có thể kế thừa từ cùng một đối tượng hoặc loại vịt thành trông giống như cùng một đối tượng, bất cứ điều gì. Miễn là bạn có thể cắm/biên dịch/kiểm tra và nó vẫn hoạt động.

Điều này tách riêng mọi thứ để bạn có thể thêm bộ nhớ cache mới mà không cần phải truy cập và sửa đổi một số đối tượng Uber-Cache. YMMV. IANAL. VÂN VÂN.

1
Paul Nathan

Câu hỏi đầu tiên, bạn có tìm thấy rất nhiều lỗi trong ứng dụng không? có lẽ quên cập nhật bộ đệm, hoặc bộ đệm xấu hoặc khó thay đổi? (Tôi nhớ một ứng dụng sẽ không thay đổi kích thước trừ khi bạn cũng thay đổi màu ... tuy nhiên bạn có thể thay đổi màu trở lại và giữ nguyên kích thước).

Những gì bạn sẽ làm là có lớp đó nhưng XÓA TẤT CẢ CÁC THÀNH VIÊN TÌNH TRẠNG. Ok đây không phải là không cần thiết nhưng tôi khuyên bạn nên thực sự bạn chỉ cần khởi tạo lớp như một lớp bình thường và PASS con trỏ vào. Đừng frigen nói ClassIWant.APtr (). LetMeChange.ANYTHINGATALL () .andhave_no_ cấu trúc ()

Nó làm việc nhiều hơn nhưng thực sự, nó ít gây nhầm lẫn. Một số nơi bạn không nên thay đổi những thứ mà bây giờ bạn không thể vì nó không còn toàn cầu. Tất cả các lớp quản lý của tôi là các lớp thông thường, chỉ cần coi nó như vậy.

1
user2528

Một chút muộn để tiệc tùng, nhưng dù sao.

Singleton là một công cụ trong hộp công cụ, giống như mọi thứ khác. Hy vọng rằng bạn có nhiều hơn trong hộp công cụ của bạn hơn là chỉ một cái búa.

Xem xét điều này:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

đấu với

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

Trường hợp thứ nhất dẫn đến khớp nối cao vv; Cách thứ 2 không có vấn đề gì @Aaronaught đang mô tả, theo như tôi có thể nói. Đó là tất cả về cách bạn sử dụng nó.

1
Evgeni