Singleton: Nên sử dụng như thế nào


291

Chỉnh sửa: Từ một câu hỏi khác, tôi đã cung cấp một câu trả lời có liên kết đến rất nhiều câu hỏi / câu trả lời về singletons: Thông tin thêm về singletons tại đây:

Vì vậy, tôi đã đọc chủ đề Singletons: thiết kế tốt hay một cái nạng?
Và cuộc cãi vã vẫn còn hoành hành.

Tôi thấy Singletons là một mẫu thiết kế (tốt và xấu).

Vấn đề với Singleton không phải là Mẫu mà là người dùng (xin lỗi mọi người). Mọi người và cha của họ nghĩ rằng họ có thể thực hiện một cách chính xác (và từ nhiều cuộc phỏng vấn tôi đã thực hiện, hầu hết mọi người không thể). Ngoài ra bởi vì mọi người nghĩ rằng họ có thể triển khai một Singleton chính xác, họ lạm dụng Mẫu và sử dụng nó trong các tình huống không phù hợp (thay thế các biến toàn cục bằng Singletons!).

Vì vậy, những câu hỏi chính cần được trả lời là:

  • Khi nào bạn nên sử dụng Singleton
  • Làm thế nào để bạn thực hiện một Singleton chính xác

Hy vọng của tôi cho bài viết này là chúng ta có thể thu thập cùng nhau ở một nơi (thay vì phải tìm kiếm trên google và tìm kiếm nhiều trang web) một nguồn có thẩm quyền khi nào (và sau đó làm thế nào) để sử dụng Singleton một cách chính xác. Cũng thích hợp sẽ là một danh sách các Chống sử dụng và các triển khai xấu phổ biến giải thích lý do tại sao chúng không hoạt động và để thực hiện tốt các điểm yếu của chúng.


Vì vậy, có được quả bóng lăn:
Tôi sẽ giơ tay lên và nói đây là những gì tôi sử dụng nhưng có lẽ có vấn đề.
Tôi thích cách xử lý "Scott Myers" của chủ đề trong cuốn sách "Hiệu quả C ++"

Các tình huống tốt để sử dụng Singletons (không nhiều):

  • Ghi nhật ký khung
  • Bể tái chế
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

ĐỒNG Ý. Cho phép nhận được một số lời chỉ trích và thực hiện khác cùng nhau.
:-)


36
Điều gì nếu sau này bạn quyết định bạn muốn nhiều logger? Hoặc nhiều nhóm chủ đề? Nếu bạn chỉ muốn một logger, thì chỉ tạo một thể hiện và biến nó thành toàn cầu. Singletons chỉ tốt nếu bạn hoàn toàn CẦN ở đó chỉ là một và nó CẦN phải là toàn cầu, IMHO.

3
Ai nói một khung chỉ có thể có 1 cá thể logger. Một singelton đại diện cho Framework. Framwork sau đó có thể cung cấp cho bạn loggers cụ thể.
Martin York

Vâng Tôi sẽ không sử dụng singeltong như một luồng. Chỉ cần đưa ra ý tưởng để châm ngòi cho câu trả lời.
Martin York

@Dan Singleton thực hiện mô hình chiến lược. Hành vi được trừu tượng hóa từ singleton. Singleton là một điểm duy nhất của mục nhập. Không có hai logger, có một logger có thể quyết định cách đăng nhập. Bạn không thể chỉ xuất ra một bản ghi cùng một lúc, không cần phải có hai bản ghi.
Lee Louviere

5
Xaade: nếu bạn muốn đăng nhập vào hai tệp thì sao? Hoặc đến một cơ sở dữ liệu? Hoặc một ổ cắm mạng? Hoặc một widget GUI? Vấn đề là, không thêm các hạn chế nhân tạo - không cần thiết. Bạn có thường xuyên vô tình tạo hai vòng lặp thay vì chỉ một vòng không? Nếu bạn chỉ muốn một logger thì chỉ tạo một.

Câu trả lời:


182

Tất cả các bạn đều sai. Đọc câu hỏi. Câu trả lời:

Sử dụng Singleton nếu:

  • Bạn cần phải có một và chỉ một đối tượng của một loại trong hệ thống

Không sử dụng Singleton nếu:

  • Bạn muốn tiết kiệm bộ nhớ
  • Bạn muốn thử một cái gì đó mới
  • Bạn muốn thể hiện bao nhiêu bạn biết
  • Bởi vì mọi người khác đang làm điều đó (Xem lập trình viên sùng bái hàng hóa trong wikipedia)
  • Trong các widget giao diện người dùng
  • Nó được coi là một bộ đệm
  • Trong chuỗi
  • Trong phiên
  • Tôi có thể đi cả ngày

Làm thế nào để tạo ra singleton tốt nhất:

  • Càng nhỏ, càng tốt. Tôi là người tối giản
  • Hãy chắc chắn rằng nó là chủ đề an toàn
  • Hãy chắc chắn rằng nó không bao giờ là null
  • Hãy chắc chắn rằng nó chỉ được tạo một lần
  • Lười hay khởi tạo hệ thống? Theo yêu cầu của bạn
  • Đôi khi, HĐH hoặc JVM tạo ra các singletons cho bạn (ví dụ: trong Java, mỗi định nghĩa lớp là một singleton)
  • Cung cấp một hàm hủy hoặc bằng cách nào đó tìm ra cách loại bỏ tài nguyên
  • Sử dụng ít bộ nhớ

14
Thật ra, tôi nghĩ bạn cũng không hoàn toàn đúng. Tôi muốn viết lại thành: "Nếu bạn cần có một và chỉ một đối tượng thuộc loại trong hệ thống VÀ bạn cần có quyền truy cập toàn cầu vào nó" Nhấn mạnh theo nhu cầu là của tôi - đừng làm điều đó nếu thuận tiện, chỉ khi bạn PHẢI có nó.

91
Bạn cũng sai rồi. Nếu bạn cần một và chỉ một đối tượng, bạn tạo một và chỉ một. Nếu không có cách logic nào mà hai trường hợp có thể được lưu lại mà không làm hỏng ứng dụng, bạn nên xem xét việc biến nó thành một đơn. Và sau đó là một khía cạnh khác, truy cập toàn cầu: Nếu bạn không cần quyền truy cập toàn cầu vào ví dụ, thì đó không nên là một đơn vị.
jalf

4
Đóng để sửa đổi, mở để mở rộng. Vấn đề là bạn không thể mở rộng một singleton thành một bộ đôi hoặc một bộ ba. Nó bị mắc kẹt như một singleton.
Lee Louviere

2
@ enzom83: Singleton S-S bao gồm mã để đảm bảo tính đơn lẻ của nó. Nếu bạn chỉ muốn một cá thể, bạn có thể mất mã đó và chỉ cần tự tạo một cá thể ... mang lại cho bạn sự tiết kiệm bộ nhớ của một cá thể, cộng với tiền tiết kiệm từ sự phản đối của mã thi hành đơn lẻ - cũng có nghĩa là không hy sinh khả năng tạo một ví dụ thứ hai nếu yêu cầu của bạn thay đổi.
cHao

4
"Nếu bạn cần phải có một và chỉ một đối tượng thuộc loại trong hệ thống" - "... và không bao giờ muốn chế nhạo đối tượng đó trong một bài kiểm tra đơn vị."
Cygon

72

Singletons cung cấp cho bạn khả năng kết hợp hai đặc điểm xấu trong một lớp. Điều đó sai ở mọi khía cạnh.

Một đơn cho bạn:

  1. Toàn cầu truy cập vào một đối tượng và
  2. Đảm bảo rằng không có nhiều hơn một đối tượng thuộc loại này có thể được tạo

Số một là đơn giản. Globals nói chung là xấu. Chúng ta không bao giờ nên làm cho các đối tượng có thể truy cập trên toàn cầu trừ khi chúng ta thực sự cần nó.

Số hai nghe có vẻ hợp lý, nhưng hãy nghĩ về nó. Lần cuối cùng bạn ** vô tình * tạo ra một đối tượng mới thay vì tham chiếu một đối tượng hiện có là khi nào? Vì đây được gắn thẻ C ++, chúng ta hãy sử dụng một ví dụ từ ngôn ngữ đó. Bạn có thường xuyên vô tình viết

std::ostream os;
os << "hello world\n";

Khi bạn định viết

std::cout << "hello world\n";

Dĩ nhiên là không. Chúng tôi không cần bảo vệ chống lại lỗi này, vì loại lỗi đó không xảy ra. Nếu vậy, phản ứng chính xác là về nhà và ngủ trong 12-20 giờ và hy vọng bạn cảm thấy tốt hơn.

Nếu chỉ cần một đối tượng, chỉ cần tạo một thể hiện. Nếu một đối tượng nên có thể truy cập toàn cầu, hãy biến nó thành toàn cầu. Nhưng điều đó không có nghĩa là không thể tạo ra các phiên bản khác của nó.

Ràng buộc "chỉ có một trường hợp là có thể" không thực sự bảo vệ chúng ta trước các lỗi có thể xảy ra. Nhưng nó không làm cho mã của chúng tôi rất khó để cấu trúc lại và duy trì. Bởi vì khá thường xuyên, chúng tôi tìm hiểu sau đó chúng tôi cần nhiều hơn một ví dụ. Chúng tôi làm có nhiều hơn một cơ sở dữ liệu, chúng tôi làm đã hơn một đối tượng cấu hình, chúng tôi muốn nhiều người khai thác gỗ. Các thử nghiệm đơn vị của chúng tôi có thể muốn có thể tạo và tạo lại các đối tượng này mỗi thử nghiệm, để lấy một ví dụ chung.

Vì vậy, một singleton nên được sử dụng nếu và chỉ nếu, chúng ta cần cả những đặc điểm nó cung cấp: Nếu chúng ta cần phải truy cập toàn cầu (mà là rất hiếm, vì globals thường nản chí) chúng ta cần để ngăn chặn bất cứ ai từ bao giờ tạo ra nhiều hơn một thể hiện của một lớp (mà âm thanh với tôi như một vấn đề thiết kế). Lý do duy nhất tôi có thể thấy cho điều này là nếu việc tạo hai trường hợp sẽ làm hỏng trạng thái ứng dụng của chúng tôi - có thể là do lớp chứa một số thành viên tĩnh hoặc độ silliness tương tự. Trong trường hợp đó, câu trả lời rõ ràng là sửa lớp đó. Nó không nên phụ thuộc vào việc là ví dụ duy nhất.

Nếu bạn cần quyền truy cập toàn cầu vào một đối tượng, hãy biến nó thành một đối tượng toàn cầu std::cout. Nhưng đừng giới hạn số lượng phiên bản có thể được tạo.

Nếu bạn hoàn toàn, tích cực cần phải hạn chế số lượng phiên bản của một lớp thành một, và không có cách nào tạo ra một thể hiện thứ hai có thể được xử lý một cách an toàn, sau đó thực thi điều đó. Nhưng đừng làm cho nó có thể truy cập trên toàn cầu.

Nếu bạn cần cả hai đặc điểm, thì 1) biến nó thành một cá thể và 2) cho tôi biết bạn cần cái đó để làm gì, bởi vì tôi đang gặp khó khăn khi tưởng tượng một trường hợp như vậy.


3
hoặc bạn có thể làm cho nó trở thành một toàn cầu, và chỉ nhận được một trong những nhược điểm của một người độc thân. Với singleton, bạn đồng thời giới hạn bản thân trong một phiên bản của cơ sở dữ liệu đó. Tại sao làm điều đó? Hoặc bạn có thể xem tại sao bạn có quá nhiều phụ thuộc đến mức danh sách khởi tạo trở nên "thực sự dài". Có phải tất cả đều cần thiết? Một số trong số họ nên được ủy quyền cho các thành phần khác? Có lẽ một số trong số chúng có thể được đóng gói cùng nhau trong một cấu trúc để chúng ta có thể vượt qua chúng như một đối số duy nhất. Có rất nhiều giải pháp, tất cả chúng đều tốt hơn singletons.
jalf

6
Vâng, một singleton có thể được biện minh ở đó. Nhưng tôi nghĩ bạn đã chứng minh quan điểm của mình rằng nó chỉ cần thiết trong những trường hợp khá kỳ lạ. Hầu hết các phần mềm không đối phó với phần cứng cày tuyết. Nhưng tôi vẫn không bị thuyết phục. Tôi đồng ý rằng trong ứng dụng thực tế của bạn, bạn chỉ muốn một trong số này. Nhưng những gì về bài kiểm tra đơn vị của bạn? Mỗi người trong số họ nên chạy một cách cô lập, vì vậy họ nên tạo ra Trình điều khiển riêng của họ - điều khó thực hiện với một singleton. Cuối cùng, tại sao đồng nghiệp của bạn sẽ tạo ra nhiều trường hợp ngay từ đầu? Đó có phải là một kịch bản thực tế để bảo vệ chống lại?
jalf

3
Và một điểm bạn đã bỏ lỡ là trong khi hai ví dụ cuối cùng của bạn có thể biện minh cho giới hạn "chỉ một trường hợp", thì chúng không làm gì để biện minh cho "ví dụ có thể truy cập toàn cầu". Tại sao trên toàn thế giới, toàn bộ cơ sở mã phải có thể truy nhập đơn vị quản trị của tổng đài điện thoại của bạn? Điểm trong một singleton là cung cấp cho bạn cả hai đặc điểm. Nếu bạn chỉ cần cái này hoặc cái kia, bạn không nên sử dụng một singleton.
jalf

2
@ jalf - Mục tiêu của tôi là chỉ cho bạn một ví dụ về nơi Singleton có ích trong tự nhiên, vì bạn không thể tưởng tượng được gì; Tôi đoán bạn không nhìn thấy nhiều lần để áp dụng nó cho dòng công việc hiện tại của bạn. Tôi chuyển sang lập trình cày tuyết từ các ứng dụng kinh doanh chỉ vì nó cho phép tôi sử dụng Singleton. :) j / k Tôi đồng ý với tiền đề của bạn rằng có nhiều cách tốt hơn để làm những việc này, bạn đã cho tôi nhiều suy nghĩ. Cảm ơn đã thảo luận!
J. Polfer

2
Sử dụng "mẫu" đơn ( AHEM! ) Để ngăn chặn mọi người ngay lập tức thêm nhiều trường hợp là ngu ngốc cũ chỉ để ngăn chặn mọi người vô tình làm như vậy. Khi tôi có một biến cục bộ foo1 loại Foo trong hàm nhỏ của mình và chỉ muốn một biến trong hàm, tôi không lo lắng rằng ai đó sẽ tạo một Foo varaible foo2 thứ hai và sử dụng nó thay vì biến ban đầu.
Thomas Eding

36

Vấn đề với singletons không phải là việc thực hiện của họ. Đó là họ kết hợp hai khái niệm khác nhau, không có khái niệm nào rõ ràng là mong muốn.

1) Singletons cung cấp một cơ chế truy cập toàn cầu đến một đối tượng. Mặc dù chúng có thể là một chủ đề an toàn hơn hoặc đáng tin cậy hơn một chút trong các ngôn ngữ mà không có thứ tự khởi tạo được xác định rõ, cách sử dụng này vẫn tương đương với đạo đức của một biến toàn cục. Đó là một biến toàn cục mặc theo một cú pháp khó xử (foo :: get_instance () thay vì g_foo, giả sử), nhưng nó phục vụ cùng một mục đích chính xác (một đối tượng duy nhất có thể truy cập trên toàn bộ chương trình) và có cùng một nhược điểm.

2) Singletons ngăn chặn nhiều lần xuất hiện của một lớp. Thật hiếm, IME, loại tính năng này nên được đưa vào một lớp. Đó thường là một điều bối cảnh hơn nhiều; rất nhiều thứ được coi là một và chỉ một thực sự chỉ xảy ra một lần duy nhất. IMO một giải pháp phù hợp hơn là chỉ tạo một thể hiện - cho đến khi bạn nhận ra rằng bạn cần nhiều hơn một thể hiện.


6
Đã đồng ý. Hai cái sai có thể làm cho một quyền theo một số người, trong thế giới thực. Nhưng trong lập trình, trộn lẫn hai ý tưởng tồi không dẫn đến kết quả tốt.
jalf

27

Một điều với các mẫu: không khái quát . Họ có tất cả các trường hợp khi chúng hữu ích và khi chúng thất bại.

Singleton có thể khó chịu khi bạn phải kiểm tra mã. Bạn thường bị mắc kẹt với một thể hiện của lớp và có thể chọn giữa việc mở một cánh cửa trong hàm tạo hoặc một phương thức nào đó để đặt lại trạng thái, v.v.

Vấn đề khác là Singleton trong thực tế không có gì khác hơn là một biến toàn cầu được ngụy trang. Khi bạn có quá nhiều trạng thái chia sẻ toàn cầu đối với chương trình của mình, mọi thứ có xu hướng quay trở lại, tất cả chúng ta đều biết điều đó.

Nó có thể làm cho việc theo dõi phụ thuộc khó khăn hơn. Khi mọi thứ phụ thuộc vào Singleton của bạn, việc thay đổi nó thành hai phần khó khăn hơn, v.v. Bạn thường bị mắc kẹt với nó. Điều này cũng cản trở sự linh hoạt. Điều tra một số khuôn khổ tiêm phụ thuộc để cố gắng giảm bớt vấn đề này.


8
Không, một singleton là nhiều hơn một biến toàn cầu trong ngụy trang. Đó là những gì làm cho nó đặc biệt xấu. Nó kết hợp tính toàn cầu (thường là xấu) với một khái niệm khác cũng xấu (đó là không cho phép lập trình viên khởi tạo một lớp nếu anh ta quyết định anh ta cần một ví dụ) Chúng thường được sử dụng làm biến toàn cục, vâng. Và sau đó họ cũng kéo theo hiệu ứng phụ khó chịu khác, và làm tê liệt cơ sở mã hóa.
jalf

7
Cũng cần lưu ý rằng những người độc thân không cần phải có khả năng tiếp cận công khai. Một singleton rất có thể là nội bộ của thư viện và không bao giờ tiếp xúc với người dùng. Vì vậy, chúng không nhất thiết phải là "toàn cầu" theo nghĩa đó.
Steven Evers

1
+1 để chỉ ra mức độ đau đớn của Singletons trong thử nghiệm.
DevSolar

1
@jalf Không cho phép ai đó tạo nhiều hơn một thể hiện của một lớp không phải là điều xấu. Nếu thực sự chỉ có một thể hiện của lớp được khởi tạo mà thực thi yêu cầu. Nếu ai đó quyết định sau đó rằng họ cần tạo một thể hiện khác, họ nên cấu trúc lại nó, bởi vì nó không bao giờ nên là một singleton ở vị trí đầu tiên.
William

2
@William: và thỉnh thoảng tôi phải có nhiều logger. Bạn không tranh cãi cho một người độc thân, nhưng cho một địa phương cũ đơn giản. Bạn muốn biết rằng một logger luôn có sẵn. Đó là những gì toàn cầu dành cho. Bạn không cần phải biết rằng không có trình ghi nhật ký nào khác có thể được khởi tạo , đó là điều mà một người độc thân thực thi. (hãy thử viết các bài kiểm tra đơn vị cho logger của bạn - điều đó dễ dàng hơn rất nhiều nếu bạn có thể tạo và hủy nó khi cần, và điều đó là không thể với một người độc thân)
jalf

13

Singletons về cơ bản cho phép bạn có trạng thái toàn cầu phức tạp trong các ngôn ngữ, điều này gây khó khăn hoặc không thể có các biến toàn cầu phức tạp.

Java đặc biệt sử dụng singletons như một sự thay thế cho các biến toàn cục, vì mọi thứ phải được chứa trong một lớp. Các biến gần nhất với các biến toàn cục là các biến tĩnh công khai, có thể được sử dụng như thể chúng là toàn cục vớiimport static

C ++ không có biến toàn cục, nhưng thứ tự xây dựng các biến lớp toàn cầu được gọi là không xác định. Như vậy, một singleton cho phép bạn trì hoãn việc tạo một biến toàn cục cho đến khi lần đầu tiên biến đó là cần thiết.

Các ngôn ngữ như Python và Ruby sử dụng singletons rất ít vì bạn có thể sử dụng các biến toàn cục trong một mô-đun thay thế.

Vậy khi nào thì tốt / xấu khi sử dụng singleton? Chính xác là khi nào thì tốt / xấu khi sử dụng biến toàn cục.


Khi nào thì một biến toàn cầu "tốt"? Đôi khi chúng là cách giải quyết tốt nhất cho một vấn đề, nhưng chúng không bao giờ "tốt".
DevSolar

1
biến toàn cục là tốt khi nó được sử dụng ở mọi nơi và mọi thứ đều có thể truy cập được. Việc thực hiện một máy turing trạng thái duy nhất có thể sử dụng một singleton.
Lee Louviere

Tôi thích lớp cảm ứng trong câu trả lời này: "khi nào thì tốt / xấu khi sử dụng toàn cầu". Cả DevSolar và Lee Louviere đều nhận được giá trị mà họ đồng ý mặc dù tại thời điểm trả lời, người ta không thể biết ai sẽ bình luận.
Praxeolitic

6

Modern C ++ Design của Alexandrescu có một singleton chung chung an toàn theo chủ đề.

Đối với giá trị 2p của tôi, tôi nghĩ điều quan trọng là phải xác định thời gian sống cho những người độc thân của bạn (khi thực sự cần thiết phải sử dụng chúng). Tôi thường không để get()chức năng tĩnh khởi tạo bất cứ thứ gì, và để lại thiết lập và hủy cho một số phần dành riêng của ứng dụng chính. Điều này giúp làm nổi bật sự phụ thuộc giữa các singletons - nhưng, như đã nhấn mạnh ở trên, tốt nhất là chỉ nên tránh chúng nếu có thể.


6
  • Làm thế nào để bạn thực hiện một Singleton chính xác

Có một vấn đề tôi chưa từng thấy đề cập đến, một điều mà tôi gặp phải ở một công việc trước đây. Chúng tôi có các singletons C ++ được chia sẻ giữa các DLL và các cơ chế thông thường để đảm bảo một thể hiện duy nhất của một lớp không hoạt động. Vấn đề là mỗi DLL có một bộ biến tĩnh riêng, cùng với EXE. Nếu hàm get_instance của bạn là nội tuyến hoặc một phần của thư viện tĩnh, mỗi DLL sẽ kết thúc với bản sao "singleton" của riêng nó.

Giải pháp là đảm bảo mã singleton chỉ được xác định trong một DLL hoặc EXE hoặc tạo một trình quản lý singleton với các thuộc tính đó để phân loại các thể hiện.


10
Yo dawg, tôi nghe nói bạn thích Singletons, vì vậy tôi đã tạo một Singleton cho Singleton của bạn, để bạn có thể chống mẫu trong khi bạn chống mẫu.
Eva

@Eva, vâng một cái gì đó như thế. Tôi đã không tạo ra vấn đề, tôi chỉ cần làm cho nó hoạt động bằng cách nào đó.
Đánh dấu tiền chuộc

5

Ví dụ đầu tiên không phải là luồng an toàn - nếu hai luồng gọi getInstance cùng một lúc, thì tĩnh đó sẽ là PITA. Một số hình thức của mutex sẽ giúp.


Đúng là được ghi chú trong các ý kiến ​​trên: * Giới hạn: Thiết kế theo luồng đơn * Xem: aristeia.com/Papers/DDJ_Jul_Aug_2004_Vvised.pdf * Đối với các sự cố liên quan đến việc khóa trong các ứng dụng đa luồng
Martin York

Singleton cổ điển chỉ có getInstance là phương thức tĩnh và phương thức cá thể cho các hoạt động khác không bao giờ có thể làm cho luồng an toàn. (tốt trừ khi bạn biến nó thành một luồng trên mỗi luồng bằng cách sử dụng bộ lưu trữ cục bộ của luồng ...)
Tobi

thậm chí trong c ++ 11 trở lên?
hg_git

5

Như những người khác đã lưu ý, những nhược điểm lớn đối với người độc thân bao gồm việc không thể mở rộng chúng và mất khả năng khởi tạo nhiều hơn một trường hợp, ví dụ cho mục đích thử nghiệm.

Một số khía cạnh hữu ích của singletons:

  1. lười biếng hoặc trả trước
  2. tiện dụng cho một đối tượng yêu cầu thiết lập và / hoặc trạng thái

Tuy nhiên, bạn không phải sử dụng một đơn để có được những lợi ích này. Bạn có thể viết một đối tượng bình thường thực hiện công việc và sau đó cho mọi người truy cập thông qua một nhà máy (một đối tượng riêng biệt). Nhà máy có thể lo lắng về việc chỉ bắt đầu một cái, và tái sử dụng nó, v.v., nếu cần. Ngoài ra, nếu bạn lập trình cho một giao diện chứ không phải là một lớp cụ thể, nhà máy có thể sử dụng các chiến lược, tức là bạn có thể chuyển đổi vào và ra các triển khai khác nhau của giao diện.

Cuối cùng, một nhà máy cho vay các công nghệ tiêm phụ thuộc như Spring, v.v.


3

Singletons rất tiện lợi khi bạn có nhiều mã được chạy khi bạn khởi tạo và phản đối. Ví dụ, khi bạn sử dụng iBatis khi bạn thiết lập một đối tượng kiên trì, nó phải đọc tất cả các cấu hình, phân tích bản đồ, đảm bảo tất cả đều chính xác, v.v. trước khi nhận mã của bạn.

Nếu bạn làm điều này mỗi lần, hiệu suất sẽ bị suy giảm nhiều. Sử dụng nó trong một singleton, bạn thực hiện cú đánh đó một lần và sau đó tất cả các cuộc gọi tiếp theo không phải thực hiện.


Các mẫu thử nghiệm thực hiện điều này là tốt, và nó linh hoạt hơn. Bạn cũng có thể sử dụng nó khi khách hàng của bạn sẽ tạo ra nhiều phiên bản của lớp đắt tiền của bạn, nhưng chỉ một số lượng hạn chế trong số họ thực sự có trạng thái khác nhau. Ví dụ, tetronimos trong Tetris.
Eva

3

Sự sụp đổ thực sự của Singletons là họ phá vỡ sự kế thừa. Bạn không thể lấy được một lớp mới để cung cấp cho bạn chức năng mở rộng trừ khi bạn có quyền truy cập vào mã nơi Singleton được tham chiếu. Vì vậy, ngoài thực tế, Singleton sẽ làm cho mã của bạn được liên kết chặt chẽ (có thể sửa được bằng Mẫu chiến lược ... hay còn gọi là Dependency Injection), nó cũng sẽ ngăn bạn đóng các phần của mã khỏi sửa đổi (thư viện dùng chung).

Vì vậy, ngay cả các ví dụ về logger hoặc nhóm luồng không hợp lệ và cần được thay thế bởi Strategies.


Bản thân logger không nên là singletons. Hệ thống nhắn tin "phát sóng" chung phải là. Bản thân logger là thuê bao của các tin nhắn quảng bá.
CashCow

Nhóm chủ đề cũng không nên là singletons. Câu hỏi chung là bạn có bao giờ muốn nhiều hơn một trong số họ không? Đúng. Khi tôi sử dụng chúng lần cuối, chúng tôi có 3 nhóm luồng khác nhau trong một ứng dụng.
CashCow

3

Hầu hết mọi người sử dụng singletons khi họ đang cố gắng làm cho bản thân cảm thấy tốt khi sử dụng một biến toàn cục. Có những cách sử dụng hợp pháp, nhưng hầu hết thời gian khi mọi người sử dụng chúng, thực tế là chỉ có một trường hợp chỉ là một thực tế tầm thường so với thực tế là nó có thể truy cập toàn cầu.


3

Bởi vì một singleton chỉ cho phép một cá thể được tạo nên nó kiểm soát sao chép cá thể một cách hiệu quả. ví dụ: bạn không cần nhiều phiên bản tra cứu - ví dụ bản đồ tra cứu morse, do đó gói nó trong một lớp đơn là thích hợp. Và chỉ vì bạn có một thể hiện duy nhất của lớp không có nghĩa là bạn cũng bị giới hạn về số lượng tham chiếu đến thể hiện đó. Bạn có thể xếp hàng các cuộc gọi (để tránh các vấn đề luồng) đến thể hiện và thay đổi hiệu quả cần thiết. Vâng, hình thức chung của một singleton là một công khai toàn cầu, bạn chắc chắn có thể sửa đổi thiết kế để tạo ra một singleton bị hạn chế truy cập hơn. Tôi chưa từng mệt mỏi với điều này trước đây nhưng tôi chắc chắn biết điều đó là có thể. Và với tất cả những người bình luận nói rằng mô hình singleton hoàn toàn xấu xa, bạn nên biết điều này:



2

Dưới đây là cách tiếp cận tốt hơn để thực hiện một mẫu singleton an toàn luồng với việc giải phóng bộ nhớ trong chính hàm hủy. Nhưng tôi nghĩ rằng hàm hủy phải là một tùy chọn vì cá thể singleton sẽ tự động bị hủy khi chương trình kết thúc:

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

Về các tình huống chúng ta cần sử dụng các lớp singleton có thể- Nếu chúng ta muốn duy trì trạng thái của cá thể trong suốt quá trình thực thi chương trình Nếu chúng ta có liên quan đến việc ghi vào nhật ký thực thi của một ứng dụng trong đó chỉ cần một phiên bản của tệp được sử dụng .... và như vậy. Nó sẽ được đánh giá cao nếu bất cứ ai có thể đề nghị tối ưu hóa trong mã trên của tôi.


2
Điều đó chắc chắn không tốt hơn. 1: Bạn không xác định ngữ nghĩa sở hữu bằng cách sử dụng con trỏ. Bạn không bao giờ nên sử dụng con trỏ trong C ++ trừ khi bạn chuẩn bị quản lý chúng. 2: Việc sử dụng khóa kiểm tra kép của bạn là cổ xưa và có nhiều cách hiện đại hơn để làm việc này. 3: Nhận xét của bạn về sự hủy diệt là ngây thơ. Việc cải tạo bộ nhớ không phải là điểm sử dụng hàm hủy mà nó là về việc dọn dẹp. Gợi ý cho một phiên bản tốt hơn: Nhìn vào câu hỏi. Các phiên bản được trình bày ở đó đã tốt hơn nhiều.
Martin York

1

Tôi sử dụng Singletons như một bài kiểm tra phỏng vấn.

Khi tôi yêu cầu nhà phát triển đặt tên cho một số mẫu thiết kế, nếu tất cả những gì họ có thể đặt tên là Singleton, họ sẽ không được thuê.


45
Các quy tắc cứng và nhanh về tuyển dụng sẽ khiến bạn bỏ lỡ rất nhiều nhân viên tiềm năng.
Karl

13
Một sự đa dạng của những kẻ ngốc tồn tại. Điều đó không có nghĩa là họ nên được xem xét để tuyển dụng. Nếu ai đó có thể đề cập đến không có mẫu thiết kế nào cả, tôi nghĩ họ sẽ thích hơn người biết đơn và không có mẫu nào khác.
jalf

3
Đối với cuốn sách kỷ lục - phản ứng của tôi là tặc lưỡi. Trong quá trình phỏng vấn thực tế của mình, tôi cố gắng đánh giá xem chúng tôi có cần dạy kèm ai đó trong C ++ hay không, và điều đó sẽ khó đến mức nào. Một số ứng cử viên yêu thích của tôi là những người KHÔNG biết C ++ từ trong ra ngoài, nhưng tôi đã có thể có một cuộc trò chuyện tuyệt vời với họ về điều đó.
Matt Cruikshank

4
Bỏ phiếu. Từ kinh nghiệm cá nhân của tôi - lập trình viên có thể không thể đặt tên cho bất kỳ mẫu nào khác ngoài Singleton, nhưng điều đó không có nghĩa là anh ta sử dụng Singletons. Cá nhân, tôi đã sử dụng singletons trong mã của mình TRƯỚC KHI tôi từng nghe về họ (tôi gọi chúng là "toàn cầu thông minh hơn" - tôi biết toàn cầu là gì). Khi tôi biết về họ, khi tôi biết về ưu và nhược điểm của họ - tôi đã ngừng sử dụng chúng. Đột nhiên, kiểm thử đơn vị trở nên thú vị hơn nhiều đối với tôi khi tôi dừng lại ... Điều đó có làm tôi trở thành một lập trình viên tồi tệ hơn không? Pfff ...
Paulius

3
Tôi cũng đang bỏ qua cho câu hỏi vô nghĩa "tên một số mẫu thiết kế". Thiết kế là để hiểu làm thế nào để áp dụng các mẫu thiết kế, không chỉ có thể bỏ qua tên của họ. Ok, điều đó có thể không đảm bảo một downvote nhưng câu trả lời này là troll-ish.
CashCow

0

Chống sử dụng:

Một vấn đề lớn với việc sử dụng đơn lẻ quá mức là mô hình ngăn chặn việc mở rộng dễ dàng và hoán đổi các triển khai thay thế. Tên lớp được mã hóa cứng ở bất cứ nơi nào singleton được sử dụng.


Bị từ chối vì 2 lý do: 1. Singleton có thể sử dụng nội bộ đa hình (ví dụ, Logger toàn cầu sử dụng các chiến lược đa hình của mục tiêu) 2. Có thể có typedef cho tên singleton, vì vậy mã thực tế phụ thuộc vào typedef.
topright gamedev

Tôi đã kết thúc việc xây dựng phiên bản đơn của mình để có thể mở rộng bằng cách sử dụng mẫu mẫu định kỳ tò mò.
Zachary Kraus

0

Tôi nghĩ rằng đây là phiên bản mạnh mẽ nhất cho C #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Đây là phiên bản được tối ưu hóa .NET :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Bạn có thể tìm thấy mô hình này tại dotfactory.com .


3
Bạn có thể loại bỏ các phần không liên quan cụ thể đến Singletons để làm cho mã dễ đọc hơn.
Martin York

Ngoài ra, phiên bản đầu tiên của bạn không an toàn cho chủ đề vì có thể sắp xếp lại đọc / ghi. Xem stackoverflow.com/questions/9666/ trộm
Thomas Danecker

5
Uh ... sai ngôn ngữ? Câu hỏi khá rõ ràng được gắn thẻ C ++ .
DevSolar

0

Mẫu đơn Meyers hoạt động đủ tốt hầu hết thời gian, và trong những dịp, nó không nhất thiết phải trả tiền để tìm kiếm bất cứ điều gì tốt hơn. Miễn là người xây dựng sẽ không bao giờ ném và không có sự phụ thuộc giữa các singletons.

Một singleton là một triển khai cho một đối tượng có thể truy cập toàn cầu (GAO kể từ bây giờ) mặc dù không phải tất cả các GAO đều là singletons.

Bản thân logger không nên là singletons nhưng phương tiện để đăng nhập lý tưởng nhất là có thể truy cập được trên toàn cầu, để giải mã nơi thông điệp tường trình được tạo từ nơi hoặc cách đăng nhập.

Đánh giá lười biếng / lười biếng là một khái niệm khác và singleton cũng thường thực hiện điều đó. Nó đi kèm với rất nhiều vấn đề của riêng nó, đặc biệt là vấn đề an toàn và vấn đề nếu nó không thành công với các ngoại lệ sao cho có vẻ như đó là một ý tưởng tốt vào thời điểm đó hóa ra lại không tuyệt vời lắm. (Một chút giống như thực hiện COW trong chuỗi).

Với ý nghĩ đó, GOAs có thể được khởi tạo như thế này:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

Nó không cần phải được thực hiện một cách thô sơ như vậy, và rõ ràng trong một thư viện được tải có chứa các đối tượng mà bạn có thể muốn một số cơ chế khác để quản lý vòng đời của chúng. (Đặt chúng vào một đối tượng mà bạn nhận được khi tải thư viện).

Khi tôi sử dụng singletons? Tôi đã sử dụng chúng cho 2 thứ - Một bảng đơn cho biết những thư viện nào đã được tải với dlopen - Trình xử lý tin nhắn mà logger có thể đăng ký và bạn có thể gửi tin nhắn đến. Cần thiết đặc biệt cho xử lý tín hiệu.


0

Tôi vẫn không hiểu tại sao một người độc thân phải toàn cầu.

Tôi sẽ tạo ra một singleton nơi tôi ẩn một cơ sở dữ liệu bên trong lớp dưới dạng một biến tĩnh không đổi riêng tư và tạo các hàm lớp sử dụng cơ sở dữ liệu mà không bao giờ để lộ cơ sở dữ liệu cho người dùng.

Tôi không thấy lý do tại sao chức năng này sẽ xấu.


Tôi không hiểu tại sao bạn nghĩ nó phải là một toàn cầu.
Martin York

Theo chủ đề này, Mọi người đều nói rằng một người độc thân phải là người toàn cầu
Zachary Kraus

1
Không. Chủ đề chỉ ra rằng một singelton có trạng thái toàn cầu. Không phải nó là một biến toàn cầu. Giải pháp bạn đề xuất có nhà nước toàn cầu. Giải pháp bạn đề xuất cũng là sử dụng biến toàn cục; một thành viên tĩnh của một lớp là một đối tượng của "Thời lượng lưu trữ tĩnh", một biến toàn cục là một đối tượng của "Thời lượng lưu trữ tĩnh". Do đó, hai cái cơ bản là giống nhau với ngữ nghĩa / phạm vi hơi khác nhau.
Martin York

Vì vậy, một biến tĩnh riêng vẫn là toàn cầu do "Thời lượng lưu trữ tĩnh"?
Zachary Kraus

1
Lưu ý: Bạn đã bỏ lỡ một cách cố ý không có bit nào được nêu. Thiết kế của bạn về việc sử dụng một thành viên "riêng tư" tĩnh không phải là xấu giống như một singelton. Bởi vì nó không giới thiệu "trạng thái đột biến toàn cầu". Nhưng nó cũng không phải là một singleton. Một singleton là một lớp được thiết kế để chỉ có một thể hiện của đối tượng có thể tồn tại. Những gì bạn đang đề xuất là trạng thái chia sẻ duy nhất cho tất cả các đối tượng của một lớp. Khái niệm khác nhau.
Martin York

0

Tôi thấy chúng hữu ích khi tôi có một lớp gói rất nhiều bộ nhớ. Ví dụ, trong một trò chơi gần đây tôi đã làm việc trên tôi có một lớp bản đồ ảnh hưởng có chứa một tập hợp các mảng bộ nhớ liền kề rất lớn. Tôi muốn tất cả được phân bổ khi khởi động, tất cả được giải phóng khi tắt máy và tôi chắc chắn chỉ muốn một bản sao của nó. Tôi cũng phải truy cập nó từ nhiều nơi. Tôi thấy mô hình singleton rất hữu ích trong trường hợp này.

Tôi chắc chắn có những giải pháp khác nhưng tôi thấy giải pháp này rất hữu ích và dễ thực hiện.


0

Nếu bạn là người đã tạo ra singleton và sử dụng nó, đừng biến nó thành singleton (điều đó không có ý nghĩa vì bạn có thể điều khiển điểm kỳ dị của đối tượng mà không biến nó thành đơn lẻ) nhưng nó có ý nghĩa khi bạn là nhà phát triển của một thư viện và bạn chỉ muốn cung cấp một đối tượng cho người dùng của mình (trong trường hợp này bạn là người đã tạo ra singleton, nhưng bạn không phải là người dùng).

Singletons là đối tượng nên sử dụng chúng làm đối tượng, nhiều người truy cập trực tiếp vào singletons thông qua việc gọi phương thức trả về nó, nhưng điều này có hại vì bạn đang làm cho mã của mình biết rằng đối tượng là singleton, tôi thích sử dụng singletons làm đối tượng, tôi vượt qua chúng thông qua hàm tạo và tôi sử dụng chúng như các đối tượng bình thường, bằng cách đó, mã của bạn không biết liệu các đối tượng này có phải là singletons hay không và điều đó làm cho các phụ thuộc rõ ràng hơn và nó giúp ích một chút cho việc tái cấu trúc ...


-1

Trong các ứng dụng máy tính để bàn (tôi biết, chỉ có chúng ta là khủng long mới viết chúng nữa!) Chúng rất cần thiết để có được các cài đặt ứng dụng toàn cầu tương đối không thay đổi - ngôn ngữ người dùng, đường dẫn để trợ giúp các tệp, tùy chọn người dùng, v.v ... mà phải đi vào mọi lớp và mọi hộp thoại .

Chỉnh sửa - tất nhiên những thứ này chỉ nên đọc!


Nhưng đây là câu hỏi; tại sao ngôn ngữ người dùng và đường dẫn đến tập tin trợ giúp phải được hợp pháp ở tất cả ?
DrPizza

2
Chúng tôi có toàn cầu cho điều đó. Không cần thiết phải biến chúng thành singletons
jalf

Biến toàn cục - sau đó làm thế nào để bạn tuần tự hóa chúng từ registry / databse? Lớp yêu tinh - vậy làm thế nào để bạn đảm bảo chỉ có một trong số họ?
Martin Beckett

@mgb: bạn tuần tự hóa chúng bằng cách đọc các giá trị từ registry / cơ sở dữ liệu và lưu trữ chúng trong các biến toàn cục (điều này có thể được thực hiện ở đầu chức năng chính của bạn). bạn đảm bảo chỉ có một đối tượng của một lớp, bằng cách chỉ tạo một đối tượng của lớp ... thực sự ... thật khó để 'grep -rn "new \ + global_group_name".' ? có thật không?
Paulius

7
@mgb: Tại sao trên trái đất tôi sẽ đảm bảo chỉ có một? Tôi chỉ cần biết rằng một phiên bản luôn đại diện cho các cài đặt hiện tại . nhưng không có lý do tại sao tôi không được phép có các đối tượng cài đặt khác ở khắp nơi. Có lẽ một ví dụ cho "các cài đặt mà người dùng hiện đang xác định, nhưng chưa được áp dụng", chẳng hạn. Hoặc một cho "cấu hình người dùng đã lưu trước đó để anh ta có thể quay lại với họ sau". Hoặc một cho mỗi bài kiểm tra đơn vị của bạn.
jalf

-1

Thực hiện khác

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
Điều đó thực sự khủng khiếp. Xem: stackoverflow.com/questions/1008019/c-singleton-design-potype/iêu để khởi tạo lười biếng tốt hơn và quan trọng hơn là phá hủy xác định.
Martin York

Nếu bạn sẽ sử dụng con trỏ, Instance()nên trả về một con trỏ, không phải là một tham chiếu. Trong .cpptệp của bạn , khởi tạo thể hiện thành null : Singleton* Singleton::instance_ = nullptr;. Và Instance()nên được thực hiện như : if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Dennis
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.