Điều gì là xấu về singletons? [đóng cửa]


1983

Mẫu đơn là một thành viên được thanh toán đầy đủ trong sách mẫu của GoF , nhưng gần đây nó dường như khá mồ côi bởi thế giới nhà phát triển. Tôi vẫn sử dụng khá nhiều singletons, đặc biệt là cho các lớp học tại nhà máy và trong khi bạn phải cẩn thận một chút về các vấn đề đa luồng (như bất kỳ lớp nào thực sự), tôi không hiểu tại sao chúng quá khủng khiếp.

Stack Overflow đặc biệt dường như cho rằng mọi người đều đồng ý rằng Singletons là ác quỷ. Tại sao?

Vui lòng hỗ trợ câu trả lời của bạn với " sự kiện, tài liệu tham khảo hoặc chuyên môn cụ thể "


6
Tôi phải nói rằng việc sử dụng một thiết kế singleton đã đốt cháy tôi gần đây khi tôi cố gắng điều chỉnh mã. Khi tôi làm điều đó trong thời gian rảnh, tôi gần như quá lười biếng để cấu trúc lại điều này. Tin xấu cho năng suất.
Marcin

71
Có rất nhiều 'khuyết điểm' trong các câu trả lời, nhưng tôi cũng muốn xem một số ví dụ hay về việc khi nào mẫu tốt, để tương phản với cái xấu ...
DGM

50
Tôi đã viết một bài đăng trên blog về chủ đề này vài tháng trước: jalf.dk/blog/2010/03/ Khăn - và hãy để tôi nói thẳng ra. Cá nhân tôi không thể nghĩ về một tình huống duy nhất trong đó một người độc thân là giải pháp phù hợp. Điều đó không có nghĩa là một tình huống như vậy không tồn tại, nhưng ... gọi chúng là hiếm là một cách nói nhẹ nhàng.
jalf

8
@AdamSmith không có nghĩa là bạn phải như vậy, nhưng điều đó có nghĩa là bạn có thể truy cập nó như thế. Và nếu bạn không có ý định truy cập nó như vậy, thì có rất ít lý do để biến nó thành một singleton ngay từ đầu. Vì vậy, tranh luận của bạn rất hiệu quả "không có hại gì khi tạo ra một người độc thân nếu chúng ta không coi nó là một người độc thân. Vâng, tuyệt vời. Xe của tôi cũng không gây ô nhiễm nếu tôi không lái xe trong đó. Nhưng sau đó thì dễ dàng hơn không mua xe ngay từ đầu .;) (tiết lộ đầy đủ: tôi thực sự không có xe hơi)
jalf

35
Phần tồi tệ nhất của toàn bộ chủ đề này là những người ghét những người độc thân hiếm khi đưa ra những gợi ý cụ thể cho những gì nên sử dụng thay thế. Ví dụ, các liên kết đến các bài báo và blog tự xuất bản thông qua bài viết SO này, tiếp tục và về lý do tại sao không sử dụng singletons (và tất cả đều là lý do tuyệt vời), nhưng chúng cực kỳ mỏng khi thay thế. Rất nhiều handwaving, mặc dù. Những người trong chúng ta đang cố gắng dạy cho các lập trình viên mới tại sao không sử dụng những người độc thân không có nhiều mẫu đối tác tốt của bên thứ ba để chỉ ra, chỉ những ví dụ giả định. Thật mệt mỏi.
Ti Strga

Câu trả lời:


1294

Diễn giải từ Nút Brian:

  1. Chúng thường được sử dụng như một ví dụ toàn cầu, tại sao điều đó lại tệ đến vậy? Bởi vì bạn ẩn các phụ thuộc của ứng dụng trong mã của mình, thay vì hiển thị chúng thông qua các giao diện. Làm một cái gì đó toàn cầu để tránh vượt qua nó là một mùi mã .

  2. Họ vi phạm nguyên tắc trách nhiệm duy nhất : nhờ thực tế rằng họ kiểm soát sự sáng tạo và vòng đời của chính họ.

  3. Chúng vốn đã khiến mã được liên kết chặt chẽ . Điều này làm cho việc giả mạo chúng ra dưới thử nghiệm khá khó khăn trong nhiều trường hợp.

  4. Họ mang trạng thái xung quanh trong suốt cuộc đời của ứng dụng. Một cú đánh khác để kiểm tra vì bạn có thể kết thúc với một tình huống trong đó các bài kiểm tra cần phải được đặt hàng, một điều không lớn đối với các bài kiểm tra đơn vị. Tại sao? Bởi vì mỗi bài kiểm tra đơn vị nên độc lập với nhau.


324
Tôi không đông y vơi bạn. Vì các bình luận chỉ được phép 600 ký tự, tôi đã viết một Bài đăng trên Blog để bình luận về điều này, vui lòng xem liên kết dưới đây. jorudolph.wordpress.com/2009/11/22/singleton-considerations
Rudolph

61
Ở điểm 1 và 4, tôi nghĩ rằng singletons rất hữu ích và trên thực tế gần như hoàn hảo cho việc lưu trữ dữ liệu (đặc biệt là từ DB). Sự gia tăng hiệu suất vượt xa sự phức tạp liên quan đến các bài kiểm tra đơn vị mô hình hóa.
Đại Bok

56
@Dai Bok: "dữ liệu lưu trữ (đặc biệt là từ DB)" sử dụng mẫu proxy để thực hiện việc này ...
paxos1977

55
Wow, phản ứng tuyệt vời. Tôi có lẽ đã sử dụng từ ngữ quá căng thẳng ở đây, vì vậy xin lưu ý rằng tôi đang trả lời một câu hỏi tiêu cực. Câu trả lời của tôi có nghĩa là một danh sách ngắn về 'các kiểu chống' xảy ra do việc sử dụng Singleton xấu. Công bố đầy đủ; Thỉnh thoảng tôi cũng sử dụng Singletons. Có nhiều câu hỏi được đặt ra trung lập hơn về SO sẽ làm cho các diễn đàn tuyệt vời khi Singletons có thể được coi là một ý tưởng tốt. Chẳng hạn, stackoverflow.com/questions/228164/ Mạnh
Jim Burger

21
Chúng không tuyệt vời để lưu vào bộ nhớ cache trong môi trường đa luồng. Bạn có thể dễ dàng đánh bại bộ đệm với nhiều luồng chiến đấu trên một tài nguyên bị giới hạn. [được duy trì bởi một người độc thân]
monksy

449

Singletons giải quyết một (và chỉ một) vấn đề.

Tài nguyên tham gia.

Nếu bạn có một số tài nguyên

( 1 ) chỉ có thể có một trường hợp duy nhất và

( 2 ) bạn cần quản lý trường hợp đơn lẻ đó,

bạn cần một người độc thân .

Không có nhiều ví dụ. Một tệp nhật ký là một tệp lớn. Bạn không muốn từ bỏ một tệp nhật ký. Bạn muốn tuôn ra, đồng bộ hóa và đóng nó đúng cách. Đây là một ví dụ về một tài nguyên được chia sẻ duy nhất phải được quản lý.

Thật hiếm khi bạn cần một người độc thân. Lý do họ xấu là vì họ cảm thấy như một người toàn cầu và họ là một thành viên được trả lương đầy đủ của cuốn sách Mẫu thiết kế GoF .

Khi bạn nghĩ rằng bạn cần một toàn cầu, có lẽ bạn đang mắc một lỗi thiết kế khủng khiếp.


43
Phần cứng cũng là một ví dụ phải không? Các hệ thống nhúng có rất nhiều phần cứng có thể sử dụng singletons - hoặc có thể là một phần lớn? <cười>
Jeff

170
Hoàn toàn đồng ý. Có rất nhiều cuộc nói chuyện "thực hành này là xấu" được xác định trôi nổi mà không có bất kỳ sự thừa nhận nào rằng thực tiễn có thể có chỗ đứng của nó. Thông thường, thực tế chỉ là "xấu" vì nó thường xuyên bị lạm dụng. Không có gì sai với mẫu Singleton miễn là nó được áp dụng phù hợp.
Damovisa

53
Các singletons theo nguyên tắc thực sự rất hiếm (và hàng đợi máy in chắc chắn không phải là một tệp và cũng không phải là tệp nhật ký - xem log4j). Thường xuyên hơn không, phần cứng là một đơn lẻ bởi sự trùng hợp chứ không phải theo nguyên tắc. Tất cả phần cứng được kết nối với PC tốt nhất là một singleton ngẫu nhiên (nghĩ nhiều màn hình, chuột, máy in, card âm thanh). Ngay cả một máy dò hạt 500 triệu đô la cũng là một đơn vị do sự trùng hợp ngẫu nhiên và hạn chế về ngân sách - không áp dụng trong phần mềm, do đó: không có đơn lẻ. Trong viễn thông, cả số điện thoại và điện thoại vật lý đều không phải là số ít (nghĩ ISDN, tổng đài).
Digitalarbeiter

23
Phần cứng có thể chỉ có một phiên bản của nhiều loại tài nguyên, nhưng phần cứng không phải là phần mềm. Không có lý do gì mà một cổng nối tiếp duy nhất trên bảng phát triển nên được mô hình hóa thành Singleton. Thật vậy, mô hình hóa nó sẽ chỉ làm cho việc chuyển sang bảng mới có hai cổng trở nên khó khăn hơn!
dash-tom-bang

19
Vì vậy, bạn sẽ viết hai lớp giống hệt nhau, sao chép rất nhiều mã, để bạn có thể có hai singletons đại diện cho hai cổng? Làm thế nào về việc chỉ sử dụng hai đối tượng SerialPort toàn cầu? Làm thế nào về việc không mô hình hóa phần cứng như các lớp ở tất cả? Và tại sao bạn lại sử dụng singletons, cũng ngụ ý truy cập toàn cầu? Bạn có muốn mọi chức năng trong mã của bạn có thể truy cập vào cổng nối tiếp không?
jalf

352

Một số snobs mã hóa coi thường họ như một toàn cầu được tôn vinh. Theo cùng một cách mà nhiều người ghét tuyên bố goto, có những người khác ghét ý tưởng về việc sử dụng toàn cầu . Tôi đã thấy một số nhà phát triển đi vào chiều dài phi thường để tránh toàn cầu vì họ coi việc sử dụng một như một sự thừa nhận thất bại. Lạ nhưng có thật.

Trong thực tế, mẫu Singleton chỉ là một kỹ thuật lập trình là một phần hữu ích trong bộ công cụ khái niệm của bạn. Thỉnh thoảng bạn có thể thấy nó là giải pháp lý tưởng và vì vậy hãy sử dụng nó. Nhưng sử dụng nó chỉ để bạn có thể tự hào về việc sử dụng một mẫu thiết kế cũng ngu ngốc như từ chối sử dụng nó bởi vì nó chỉ là một toàn cầu .


17
Thất bại mà tôi thấy ở Singleton là mọi người sử dụng chúng thay vì toàn cầu vì bằng cách nào đó họ "tốt hơn". Vấn đề là (như tôi thấy), rằng những điều mà Singleton mang đến bàn trong những trường hợp này là không liên quan. (Ví dụ, việc xây dựng khi sử dụng là không quan trọng để thực hiện trong một người không độc thân, ví dụ, ngay cả khi nó không thực sự sử dụng hàm tạo để thực hiện.)
dash-tom-bang

21
Lý do "chúng tôi" xem thường họ là vì "chúng tôi" rất thường xuyên thấy họ sử dụng sai và quá thường xuyên. "Chúng tôi" biết rằng họ có vị trí của mình.
Bjarke Freund-Hansen

5
@ Phil, Bạn nói rằng "thỉnh thoảng bạn có thể thấy đó là giải pháp lý tưởng và vì vậy hãy sử dụng nó". Ok, vậy chính xác trong đó trường hợp chúng tôi sẽ tìm thấy một singleton hữu ích?
Pacerier

22
@Pacerier, bất cứ khi nào tất cả các điều kiện sau giữ: (1) bạn chỉ cần một trong số đó, (2) bạn cần chuyển một điều đó làm đối số trong một số lượng lớn các lệnh gọi phương thức, (3) bạn sẵn sàng chấp nhận cơ hội phải tái cấu trúc một ngày nào đó để đổi lấy việc giảm ngay lập tức kích thước và độ phức tạp của mã của bạn bằng cách không chuyển thứ cực đoan đi khắp nơi.
antinome

18
Tôi không cảm thấy rằng điều này trả lời bất cứ điều gì, nó chỉ nói rằng "đôi khi nó có thể phù hợp, đôi khi nó có thể không". OK, nhưng tại sao, và khi nào? Điều gì làm cho câu trả lời này nhiều hơn một đối số để kiểm duyệt ?
Guildenstern

219

Misko Hevery, từ Google, có một số bài viết thú vị về chính xác chủ đề này ...

Singletons là Pathological Liars có một ví dụ thử nghiệm đơn vị minh họa cách các singletons có thể gây khó khăn cho việc tìm ra chuỗi phụ thuộc và bắt đầu hoặc kiểm tra một ứng dụng. Đó là một ví dụ khá cực đoan về lạm dụng, nhưng quan điểm mà anh ta đưa ra vẫn còn hiệu lực:

Singletons không có gì hơn nhà nước toàn cầu. Trạng thái toàn cầu làm cho nó để các đối tượng của bạn có thể bí mật nắm giữ những thứ không được khai báo trong API của họ và do đó, Singletons biến API của bạn thành những kẻ nói dối bệnh lý.

Trường hợp có tất cả các Singletons đưa ra quan điểm rằng việc tiêm phụ thuộc đã giúp dễ dàng đưa các cá thể đến các nhà xây dựng yêu cầu chúng, điều này làm giảm bớt nhu cầu tiềm ẩn đằng sau những Singletons xấu, toàn cầu đã giải mã trong bài viết đầu tiên.


8
Các bài viết của Misko về chủ đề này là điều tốt nhất đang diễn ra xung quanh nó vào lúc này.
Chris Mayer

22
Liên kết đầu tiên không thực sự giải quyết vấn đề với singletons, mà là giả sử một sự phụ thuộc tĩnh bên trong lớp. Có thể sửa ví dụ đã cho bằng cách truyền tham số, nhưng vẫn sử dụng singletons.
DGM

17
@DGM: Chính xác - trên thực tế, có một sự mất kết nối logic rất lớn giữa phần 'lý do' của bài viết và phần 'Singletons là nguyên nhân'.
Harper Shelby

1
Bài viết về Thẻ tín dụng của Misko là một ví dụ cực đoan về việc sử dụng sai mẫu này.
Alex

2
Đọc bài viết thứ hai Singletons thực sự là một phần của mô hình đối tượng được mô tả. Tuy nhiên, thay vì có thể truy cập trên toàn cầu, chúng là riêng tư đối với các đối tượng Factory.
FruitBreak

121

Tôi nghĩ sự nhầm lẫn này là do mọi người không biết ứng dụng thực sự của mẫu Singleton. Tôi không thể nhấn mạnh điều này đủ. Singleton không phải là một mô hình để bọc toàn cầu. Mẫu đơn chỉ nên được sử dụng để đảm bảo rằng một và chỉ một thể hiện của một lớp nhất định tồn tại trong thời gian chạy.

Mọi người nghĩ Singleton là xấu xa vì họ đang sử dụng nó cho toàn cầu. Chính vì sự nhầm lẫn này mà Singleton bị coi thường. Xin đừng nhầm lẫn Singletons và toàn cầu. Nếu được sử dụng cho mục đích mà nó được dự định, bạn sẽ thu được những lợi ích cực kỳ từ mẫu Singleton.


20
Một ví dụ đó, có sẵn trong ứng dụng là gì? Oh. Toàn cầu . "Singleton không phải là một mô hình để bao bọc toàn cầu" là ngây thơ hoặc sai lệch, theo định nghĩa , mô hình này bao bọc một lớp xung quanh một thể hiện toàn cầu.
cHao

26
Tất nhiên, bạn có thể tự do tạo một thể hiện của lớp khi ứng dụng của bạn khởi động và đưa cá thể đó, thông qua một giao diện, vào bất cứ thứ gì sử dụng nó. Việc thực hiện không nên quan tâm rằng chỉ có thể có một.
Scott Whitlock

4
@Dainius: Cuối cùng thì thực sự không. Chắc chắn, bạn không được tự ý thay thế cá thể bằng cái khác. (Ngoại trừ những khoảnh khắc gây ấn tượng khi bạn làm . Tôi thực sự đã thấy setInstancecác phương pháp trước đây.) Tuy nhiên, điều đó hầu như không quan trọng - rằng weenie "cần" một người độc thân cũng không biết một điều kỳ quặc về việc đóng gói hoặc có gì sai với nhà nước toàn cầu có thể thay đổi, vì vậy ông hữu ích (?) cung cấp setters cho mọi. Độc thân. cánh đồng. (Và vâng, điều này xảy ra. Rất nhiều . Gần như mọi người độc thân tôi từng thấy trong tự nhiên đều có thể thay đổi theo thiết kế, và thường lúng túng như vậy.)
cHao

4
@Dainius: Ở một mức độ lớn, chúng tôi đã có. "Thích thành phần hơn thừa kế" đã là một điều khá lâu nay. Tuy nhiên, khi thừa kế giải pháp tốt nhất, bạn hoàn toàn có thể sử dụng nó. Điều tương tự với singletons, global, threading, gotov.v. Chúng có thể hoạt động trong nhiều trường hợp, nhưng thật lòng mà nói, "công việc" là không đủ - nếu bạn muốn đi ngược lại với sự khôn ngoan thông thường, bạn nên có thể chứng minh cách tiếp cận của mình là tốt hơn so với các giải pháp thông thường. Và tôi vẫn chưa thấy một trường hợp như vậy cho mẫu Singleton.
cHao

3
Vì sợ chúng ta nói chuyện với nhau, tôi không chỉ nói về một trường hợp có sẵn trên toàn cầu. Có rất nhiều trường hợp cho điều đó. Điều tôi đang nói (đặc biệt khi tôi nói "capital-S Singleton") là mẫu Singleton của GoF, nó nhúng phiên bản toàn cầu duy nhất đó vào chính lớp, phơi bày nó thông qua một getInstancephương thức có tên tương tự và ngăn chặn sự tồn tại của một ví dụ thứ hai. Thành thật mà nói, tại thời điểm đó, bạn thậm chí có thể không có một trường hợp.
cHao

72

Một điều khá tệ về singletons là bạn không thể mở rộng chúng rất dễ dàng. Về cơ bản, bạn phải xây dựng một số kiểu trang trí hoặc một số thứ như vậy nếu bạn muốn thay đổi hành vi của họ. Ngoài ra, nếu một ngày bạn muốn có nhiều cách để làm điều đó, việc thay đổi có thể khá đau đớn, tùy thuộc vào cách bạn đặt mã của mình.

Một điều cần lưu ý, nếu bạn sử dụng singletons, hãy cố gắng chuyển chúng cho bất cứ ai cần chúng thay vì để chúng truy cập trực tiếp ... Nếu không, nếu bạn từng chọn có nhiều cách để làm điều mà singleton làm, nó sẽ là khá khó để thay đổi vì mỗi lớp nhúng một phụ thuộc nếu nó truy cập trực tiếp vào singleton.

Nên về cơ bản:

public MyConstructor(Singleton singleton) {
    this.singleton = singleton;
}

thay vì:

public MyConstructor() {
    this.singleton = Singleton.getInstance();
}

Tôi tin rằng loại mô hình này được gọi là tiêm phụ thuộc và thường được coi là một điều tốt.

Giống như bất kỳ mẫu nào mặc dù ... Hãy suy nghĩ về nó và xem xét liệu việc sử dụng nó trong tình huống cụ thể có phù hợp hay không ... Các quy tắc được thực hiện thường bị phá vỡ, và các mẫu không nên được áp dụng willy nilly mà không cần suy nghĩ.


17
Heh, nếu bạn làm điều này ở mọi nơi, thì bạn cũng sẽ phải chuyển qua một tài liệu tham khảo đến singelton ở mọi nơi, và do đó bạn không còn có một singleton nữa. :) (Đó thường là một điều tốt IMO.)
Bjarke Freund-Hansen

2
@ BjarkeFreund-Hansen - Vô nghĩa, bạn đang nói về cái gì vậy? Một singleton chỉ đơn giản là một thể hiện của một lớp được tạo ra một lần. Tham chiếu một Singleton như vậy không sao chép đối tượng thực tế, nó chỉ tham chiếu nó - bạn vẫn có cùng một đối tượng (đọc: Singleton).
M. Mimpen

2
@ M.Mimpen: Không, Singleton S (vốn đang được thảo luận ở đây) là một thể hiện của một lớp (a) đảm bảo rằng chỉ có một thể hiện tồn tại và (b) được truy cập thông qua chính lớp đó tích hợp điểm truy cập toàn cầu. Nếu bạn đã tuyên bố rằng không có gì nên gọi getInstance(), thì (b) không còn đúng nữa.
cHao

2
@cHao Tôi không theo dõi bạn hoặc bạn không hiểu người mà tôi nhận xét - đó là với Bjarke Freund-Hansen. Bjarke tuyên bố rằng có một số tài liệu tham khảo về một kết quả đơn lẻ có một số singletons. Điều đó chắc chắn là không đúng vì không có bản sao sâu.
M. Mimpen

6
@ M.Mimpen: Tôi lấy nhận xét của anh ấy để tham khảo thêm về hiệu ứng ngữ nghĩa. Khi bạn ngoài vòng pháp luật gọi đến getInstance(), bạn đã đưa ra một sự khác biệt hữu ích giữa mẫu Singleton và một tham chiếu thông thường. Theo như phần còn lại của mã, đơn lẻ không còn là một tài sản. Chỉ người gọi getInstance()bao giờ cần biết hoặc thậm chí quan tâm có bao nhiêu trường hợp. Chỉ với một người gọi, lớp học sẽ tốn nhiều công sức và linh hoạt hơn để thực thi một cách đáng tin cậy so với việc người gọi chỉ cần lưu trữ một tài liệu tham khảo và sử dụng lại nó.
cHao

69

Bản thân mẫu đơn không phải là một vấn đề. Vấn đề là mô hình thường được sử dụng bởi những người phát triển phần mềm với các công cụ hướng đối tượng mà không nắm vững các khái niệm OO. Khi singletons được giới thiệu trong bối cảnh này, chúng có xu hướng phát triển thành các lớp không thể quản lý có chứa các phương thức trợ giúp cho mỗi lần sử dụng.

Singletons cũng là một vấn đề từ góc độ thử nghiệm. Họ có xu hướng làm cho các bài kiểm tra đơn vị bị cô lập khó viết. Nghịch đảo điều khiển (IoC) và tiêm phụ thuộc là các mô hình nhằm khắc phục vấn đề này theo cách hướng đối tượng, cho vay để thử nghiệm đơn vị.

Trong một môi trường rác được thu thập, singletons có thể nhanh chóng trở thành một vấn đề liên quan đến quản lý bộ nhớ.

Ngoài ra còn có kịch bản đa luồng trong đó singletons có thể trở thành nút cổ chai cũng như vấn đề đồng bộ hóa.


4
tôi biết đó là chủ đề nhiều năm tuổi. hi @Kimoz u nói: - singletons có thể nhanh chóng trở thành một vấn đề liên quan đến quản lý bộ nhớ. muốn giải thích chi tiết hơn về loại vấn đề nào có thể xảy ra liên quan đến việc thu gom rác & rác.
Thomas

@Kimoz, Câu hỏi đặt ra là "Tại sao mẫu singleton không phải là vấn đề trong chính nó?" và bạn chỉ đơn thuần lặp lại điểm nhưng cung cấp thậm chí không phải là trường hợp sử dụng hợp lệ duy nhất của mẫu đơn.
Pacerier

@Thomas, bởi vì một định nghĩa đơn lẻ chỉ tồn tại trong một trường hợp. Vì vậy, thường rất phức tạp khi chỉ định tham chiếu duy nhất cho null. Nó có thể được thực hiện, nhưng điều đó có nghĩa là bạn hoàn toàn kiểm soát điểm mà sau đó singleton không được sử dụng trong ứng dụng của bạn. Và điều này rất hiếm, và thường hoàn toàn trái ngược với những gì người đam mê singleton đang tìm kiếm: một cách đơn giản để làm cho một cá thể duy nhất luôn có thể truy cập được. Trên một số framworks DI như Guice hoặc Dagger, không thể thoát khỏi một singleton và nó tồn tại mãi trong ký ức. (Mặc dù container được cung cấp singletons tốt hơn nhiều so với những người làm tại nhà).
Snicolas

54

Một singleton được thực hiện bằng phương pháp tĩnh. Các phương thức tĩnh được tránh bởi những người thực hiện kiểm tra đơn vị vì chúng không thể bị chế giễu hoặc khai thác. Hầu hết mọi người trên trang web này là những người ủng hộ thử nghiệm đơn vị lớn. Quy ước được chấp nhận rộng rãi nhất để tránh chúng là sử dụng phép đảo ngược của mẫu điều khiển .


9
Điều đó nghe có vẻ giống như một vấn đề với kiểm thử đơn vị có thể kiểm tra các đối tượng (đơn vị), hàm (đơn vị), toàn bộ thư viện (đơn vị), nhưng không thành công với bất kỳ thứ gì tĩnh trong lớp (cũng là đơn vị).
v010dya

2
Không phải bạn cho rằng để moc tất cả các tài liệu tham khảo bên ngoài? Nếu bạn là vậy, thì có vấn đề gì với moc singleton, nếu không, bạn có thực sự đang thử nghiệm đơn vị không?
Dainius

@Dainius: Các trường hợp chế nhạo ít rắc rối hơn các lớp chế nhạo. Bạn có thể trích xuất một cách hợp lý lớp đang được kiểm tra từ phần còn lại của ứng dụng và kiểm tra với một Singletonlớp giả . Tuy nhiên, điều đó vô cùng phức tạp trong quá trình thử nghiệm. Đối với một, bây giờ bạn cần có thể hủy tải các lớp theo ý muốn (không thực sự là một tùy chọn trong hầu hết các ngôn ngữ) hoặc bắt đầu một VM mới cho mỗi bài kiểm tra (đọc: các bài kiểm tra có thể mất hàng ngàn lần). Tuy nhiên, đối với hai người, sự phụ thuộc vào Singletonlà một chi tiết triển khai hiện đang rò rỉ trên tất cả các thử nghiệm của bạn.
cHao

Powermock có thể giả định các công cụ tĩnh.
Snicolas

không chế nhạo một đối tượng có nghĩa là tạo ra đối tượng thực sự? Nếu việc chế nhạo không tạo ra một đối tượng thực thì tại sao lớp đó là singleton hay phương thức là tĩnh?
Arun Raaj

45

Singletons cũng rất tệ khi phân cụm . Bởi vì sau đó, bạn không còn "chính xác một singleton" trong ứng dụng của mình nữa.

Xem xét tình huống sau: Là nhà phát triển, bạn phải tạo một ứng dụng web truy cập cơ sở dữ liệu. Để đảm bảo rằng các cuộc gọi cơ sở dữ liệu đồng thời không xung đột lẫn nhau, bạn tạo một luồng lưu SingletonDao:

public class SingletonDao {
    // songleton's static variable and getInstance() method etc. omitted
    public void writeXYZ(...){
        synchronized(...){
            // some database writing operations...
        }
    }
}

Vì vậy, bạn chắc chắn rằng chỉ có một singleton trong ứng dụng của bạn tồn tại và tất cả các cơ sở dữ liệu đều đi qua cái này và chỉ một SingletonDao. Môi trường sản xuất của bạn bây giờ trông như thế này: Độc thân

Mọi thứ đều ổn cho đến nay.

Bây giờ, hãy xem xét bạn muốn thiết lập nhiều phiên bản của ứng dụng web của bạn trong một cụm. Bây giờ, bạn đột nhiên có một cái gì đó như thế này:

Nhiều người độc thân

Nghe có vẻ lạ, nhưng bây giờ bạn có nhiều singletons trong ứng dụng của mình . Và đó chính xác là điều mà một người độc thân không được coi là: Có nhiều đối tượng của nó. Điều này đặc biệt tệ nếu bạn, như trong ví dụ này, muốn thực hiện các cuộc gọi được đồng bộ hóa tới cơ sở dữ liệu.

Tất nhiên đây là một ví dụ về việc sử dụng đơn lẻ. Nhưng thông điệp của ví dụ này là: Bạn không thể tin rằng có chính xác một trường hợp của một đơn trong ứng dụng của bạn - đặc biệt là khi phân cụm.


4
nếu bạn không biết cách thực hiện singleton, bạn không nên làm điều đó. Nếu bạn không biết những gì bạn đang làm, bạn nên tìm thấy điều đó trước tiên và chỉ sau đó làm những gì bạn cần.
Dainius

3
Điều này thật thú vị, tôi có một câu hỏi. Vậy chính xác thì vấn đề là gì nếu các singletons - mỗi máy trên một máy / JVM khác nhau - đang kết nối với một cơ sở dữ liệu? Phạm vi Singletons chỉ dành cho JVM cụ thể đó và điều đó vẫn đúng ngay cả trong một cụm. Thay vì chỉ nói một cách triết lý rằng tình huống cụ thể này là xấu vì ý định của chúng tôi là một đối tượng duy nhất trên ứng dụng, tôi sẽ rất vui khi thấy bất kỳ vấn đề kỹ thuật nào có thể phát sinh do sự sắp xếp này.
Saurabh Patil

39
  1. Nó dễ dàng (ab) được sử dụng như một biến toàn cục.
  2. Các lớp phụ thuộc vào singletons tương đối khó hơn để kiểm tra đơn vị.

33

Độc quyền là ma quỷ và những người độc thân với trạng thái không đọc / biến đổi là vấn đề 'thực sự' ...

Sau khi đọc Singletons là những kẻ nói dối bệnh lý như được đề xuất trong câu trả lời của jason, tôi đã xem qua mẩu tin nhỏ này cung cấp ví dụ được trình bày tốt nhất về cách những người độc thân thường bị lạm dụng.

Toàn cầu là xấu vì:

  • a. Nó gây ra xung đột không gian tên
  • b. Nó phơi bày nhà nước một cách không chính đáng

Khi nói đến Singletons

  • a. Cách gọi OO rõ ràng, ngăn chặn các xung đột, vì vậy điểm a. không phải là một vấn đề
  • b. Singletons không có nhà nước là (như các nhà máy) không phải là một vấn đề. Singletons với trạng thái một lần nữa có thể thuộc hai loại, những loại không thay đổi hoặc viết một lần và đọc nhiều (tệp cấu hình / thuộc tính). Đây không phải là xấu. Singletons Mutable, loại chủ sở hữu tài liệu tham khảo là những người mà bạn đang nói đến.

Trong tuyên bố cuối cùng, ông đề cập đến khái niệm 'singletons là kẻ nói dối' của blog.

Làm thế nào điều này áp dụng cho Monopoly?

Để bắt đầu một trò chơi độc quyền, đầu tiên:

  • chúng tôi thiết lập các quy tắc trước tiên để mọi người ở trên cùng một trang
  • mọi người đều được bắt đầu như nhau khi bắt đầu trò chơi
  • chỉ có một bộ quy tắc được trình bày để tránh nhầm lẫn
  • các quy tắc không được phép thay đổi trong suốt trò chơi

Bây giờ, đối với bất kỳ ai chưa thực sự chơi độc quyền, những tiêu chuẩn này là lý tưởng nhất. Một thất bại trong độc quyền là khó nuốt vì, độc quyền là về tiền, nếu bạn thua bạn phải chăm chỉ theo dõi những người chơi còn lại kết thúc trò chơi, và tổn thất thường nhanh chóng và tan nát. Vì vậy, các quy tắc thường bị xoắn vào một số thời điểm để phục vụ lợi ích cá nhân của một số người chơi với chi phí của những người khác.

Vì vậy, bạn đang chơi độc quyền với bạn bè Bob, Joe và Ed. Bạn đang nhanh chóng xây dựng đế chế của mình và tiêu thụ thị phần với tốc độ theo cấp số nhân. Đối thủ của bạn đang yếu đi và bạn bắt đầu ngửi thấy mùi máu (theo nghĩa bóng). Bob, bạn thân của bạn đã đặt tất cả số tiền của mình vào việc bế tắc càng nhiều tài sản có giá trị thấp càng tốt nhưng anh ta không nhận được lợi tức đầu tư cao như cách anh ta mong đợi. Bob, như một cơn xui xẻo, rơi xuống Boardwalk của bạn và bị loại khỏi trò chơi.

Bây giờ trò chơi đi từ lăn xúc xắc thân thiện đến kinh doanh nghiêm túc. Bob đã được làm ví dụ về sự thất bại và Joe và Ed không muốn kết thúc giống như 'anh chàng đó'. Vì vậy, trở thành người chơi hàng đầu của bạn, bất ngờ, trở thành kẻ thù. Joe và Ed bắt đầu thực hành các giao dịch dưới bàn, tiêm tiền sau, đổi nhà bị đánh giá thấp và nói chung là bất cứ điều gì làm bạn yếu đi như một người chơi cho đến khi một trong số họ vươn lên dẫn đầu.

Sau đó, thay vì một trong số họ chiến thắng, quá trình bắt đầu lại. Thật bất ngờ, một bộ quy tắc hữu hạn trở thành mục tiêu chuyển động và trò chơi biến thành kiểu tương tác xã hội sẽ tạo nên nền tảng của mọi chương trình truyền hình thực tế được đánh giá cao kể từ Survivor. Tại sao, bởi vì các quy tắc đang thay đổi và không có sự đồng thuận về cách thức / lý do / những gì họ được cho là đại diện, và quan trọng hơn, không có ai đưa ra quyết định. Mỗi người chơi trong trò chơi, tại thời điểm đó, đang đưa ra các quy tắc và sự hỗn loạn của riêng mình xảy ra cho đến khi hai trong số những người chơi quá mệt mỏi để theo kịp trò chơi đố chữ và từ từ bỏ cuộc.

Vì vậy, nếu một quy tắc cho một trò chơi đại diện chính xác cho một người độc thân, thì quy tắc độc quyền sẽ là một ví dụ về lạm dụng.

Làm thế nào điều này áp dụng cho lập trình?

Ngoài tất cả các vấn đề về đồng bộ hóa và an toàn luồng rõ ràng mà các singletons có thể thay đổi hiện diện ... Nếu bạn có một bộ dữ liệu, có khả năng được đọc / thao tác bởi nhiều nguồn khác nhau đồng thời và tồn tại trong suốt thời gian thực thi ứng dụng, có lẽ là thời điểm tốt để lùi lại và hỏi "tôi có đang sử dụng đúng loại cấu trúc dữ liệu ở đây không".

Cá nhân, tôi đã thấy một lập trình viên lạm dụng một singleton bằng cách sử dụng nó như một loại lưu trữ cơ sở dữ liệu đa luồng xoắn trong một ứng dụng. Đã làm việc trực tiếp với mã, tôi có thể chứng thực rằng nó chậm (vì tất cả các khóa luồng cần thiết để làm cho nó an toàn cho luồng) và là cơn ác mộng khi làm việc (vì tính chất không thể đoán trước / không liên tục của các lỗi đồng bộ hóa) và gần như không thể kiểm tra trong điều kiện 'sản xuất'. Chắc chắn, một hệ thống có thể đã được phát triển bằng cách sử dụng bỏ phiếu / báo hiệu để khắc phục một số vấn đề về hiệu năng nhưng điều đó sẽ không giải quyết được vấn đề khi thử nghiệm và tại sao phải lo lắng khi cơ sở dữ liệu 'thực' có thể thực hiện chức năng tương tự mạnh mẽ hơn nhiều / cách mở rộng.

Một Singleton chỉ là một lựa chọn nếu bạn cần những gì một singleton cung cấp. Một ví dụ chỉ đọc một của một đối tượng. Quy tắc tương tự cũng sẽ xếp tầng cho các thuộc tính / thành viên của đối tượng.


1
Điều gì xảy ra nếu singleton rút tiền một số dữ liệu?
Yola

@Yola Nó sẽ cắt giảm số lượng ghi nếu singleton được lên lịch để cập nhật theo lịch trình được xác định trước và / hoặc cố định, do đó làm giảm sự tranh chấp chủ đề. Tuy nhiên, bạn sẽ không thể kiểm tra chính xác bất kỳ mã nào tương tác với singleton trừ khi bạn giả định một trường hợp không đơn lẻ mô phỏng cùng một cách sử dụng. Những người TDD có thể sẽ có một sự phù hợp nhưng nó sẽ hoạt động.
Evan Plaice

@EvanPlaice: Ngay cả với một bản giả, bạn sẽ gặp một số vấn đề với mã Singleton.getInstance(). Một ngôn ngữ hỗ trợ sự phản chiếu có thể có thể giải quyết vấn đề đó bằng cách đặt trường nơi Trường hợp thực sự được lưu trữ. IMO, tuy nhiên, các bài kiểm tra trở nên kém tin cậy hơn một khi bạn đã thực hiện trò đùa với trạng thái riêng tư của một lớp khác.
cHao

28

Singleton không phải là một trường hợp duy nhất!

Không giống như những câu trả lời khác, tôi không muốn nói về những gì không ổn với Singletons nhưng để cho bạn thấy chúng mạnh mẽ và tuyệt vời như thế nào khi được sử dụng đúng!

  • Vấn đề : Singleton có thể là một thách thức trong môi trường đa luồng
    Giải pháp : Sử dụng quy trình bootstrap đơn luồng để khởi tạo tất cả các phụ thuộc của singleton.
  • Vấn đề : Thật khó để chế nhạo singletons.
    Giải pháp : Sử dụng phương thức Mô hình nhà máy để chế nhạo

Bạn có thể ánh xạ MyModeltới TestMyModellớp kế thừa nó, ở mọi nơi khi MyModelđược tiêm, bạn sẽ nhận được TestMyModelBarsead. - Vấn đề : Singletons có thể gây rò rỉ bộ nhớ vì chúng không bao giờ được xử lý.
Giải pháp : Vâng, xử lý chúng! Thực hiện một cuộc gọi lại trong ứng dụng của bạn để xử lý đúng một singletons, bạn nên xóa mọi dữ liệu được liên kết với chúng và cuối cùng: xóa chúng khỏi Factory.

Như tôi đã nói ở tiêu đề singleton không phải là về một trường hợp duy nhất.

  • Singletons cải thiện khả năng đọc : Bạn có thể nhìn vào lớp của mình và xem những gì singleton được tiêm để tìm ra sự phụ thuộc của nó là gì.
  • Singletons cải thiện việc bảo trì : Khi bạn đã loại bỏ một phụ thuộc khỏi một lớp bạn vừa xóa một số tiêm đơn lẻ, bạn không cần phải chỉnh sửa một liên kết lớn của các lớp khác mà chỉ cần di chuyển sự phụ thuộc của bạn xung quanh (Đây là mã có mùi cho tôi @Jim Burger )
  • Singletons cải thiện bộ nhớ và hiệu suất : Khi có điều gì đó xảy ra trong ứng dụng của bạn và phải mất một chuỗi cuộc gọi dài để cung cấp, bạn đang lãng phí bộ nhớ và hiệu suất, bằng cách sử dụng Singleton, bạn đang cắt trung gian và cải thiện hiệu suất và việc sử dụng bộ nhớ của bạn ( bằng cách tránh phân bổ biến cục bộ không cần thiết).

2
Điều này không giải quyết vấn đề chính của tôi với Singletons, họ cho phép truy cập vào trạng thái toàn cầu từ bất kỳ trong số hàng ngàn lớp trong dự án của bạn.
Truyền thuyết Chiều dài

8
Đó sẽ là mục đích ...
Ilya Gazman

LegendLpm Tại sao điều đó là sai? Ví dụ, trong ứng dụng của tôi, tôi có một Settingsđối tượng singleton có thể truy cập được từ bất kỳ widget nào để mỗi widget biết về cách định dạng các số được hiển thị. Nếu nó không phải là một đối tượng có thể truy cập toàn cầu, tôi sẽ phải đưa nó vào hàm tạo cho mỗi và mọi widget trong hàm tạo và giữ tham chiếu đến nó như một biến thành viên. Đây là một sự lãng phí khủng khiếp của bộ nhớ và thời gian.
VK

23

Câu trả lời của tôi về cách Singletons luôn tệ, "họ khó làm đúng". Nhiều thành phần nền tảng của ngôn ngữ là singletons (lớp, hàm, không gian tên và thậm chí cả toán tử), cũng như các thành phần trong các khía cạnh khác của máy tính (localhost, tuyến mặc định, hệ thống tệp ảo, v.v.), và nó không phải là ngẫu nhiên. Mặc dù chúng gây ra rắc rối và thất vọng theo thời gian, chúng cũng có thể làm cho rất nhiều thứ hoạt động tốt hơn rất nhiều.

Hai vấn đề lớn nhất tôi thấy là: coi nó như một toàn cầu & không xác định việc đóng cửa Singleton.

Mọi người đều nói về Singleton như toàn cầu, bởi vì về cơ bản chúng là như vậy. Tuy nhiên, rất nhiều (đáng buồn thay, không phải tất cả) về tính xấu trong toàn cầu không thực chất đến từ toàn cầu, mà là cách bạn sử dụng nó. Tương tự với Singletons. Trên thực tế nhiều hơn vì "một trường hợp duy nhất" thực sự không cần có nghĩa là "có thể truy cập toàn cầu". Đó là một sản phẩm phụ tự nhiên hơn, và với tất cả những điều xấu mà chúng ta biết đến từ nó, chúng ta không nên vội vàng khai thác khả năng tiếp cận toàn cầu. Khi các lập trình viên nhìn thấy một Singleton, họ dường như luôn truy cập trực tiếp thông qua phương thức cá thể của nó. Thay vào đó, bạn nên điều hướng đến nó giống như bất kỳ đối tượng nào khác. Hầu hết các mã thậm chí không nên biết rằng nó đang xử lý một Singleton (khớp nối lỏng lẻo, phải không?). Nếu chỉ một chút mã truy cập vào đối tượng như nó là toàn cầu, rất nhiều tác hại sẽ được hoàn tác.

Bối cảnh Singleton cũng thực sự quan trọng. Đặc điểm xác định của Singleton là "chỉ có một", nhưng sự thật là "chỉ có một" trong một số loại bối cảnh / không gian tên. Chúng thường là một trong số: một cho mỗi luồng, tiến trình, địa chỉ IP hoặc cụm, nhưng cũng có thể là một trên mỗi bộ xử lý, máy, không gian tên ngôn ngữ / trình tải lớp / bất cứ thứ gì, mạng con, Internet, v.v.

Sai lầm khác, ít phổ biến hơn là bỏ qua lối sống Singleton. Chỉ vì chỉ có một điều không có nghĩa là Singleton là một người toàn năng "luôn luôn và sẽ luôn như vậy", nói chung là không mong muốn (các đối tượng không có sự khởi đầu và kết thúc vi phạm tất cả các loại giả định hữu ích trong mã và chỉ nên được sử dụng trong hoàn cảnh tuyệt vọng nhất

Nếu bạn tránh được những sai lầm đó, Singletons vẫn có thể là một PITA, bit đã sẵn sàng để thấy rất nhiều vấn đề tồi tệ nhất được giảm thiểu đáng kể. Hãy tưởng tượng một Java Singleton, được định nghĩa rõ ràng là một lần cho mỗi trình nạp lớp (có nghĩa là nó cần một chính sách an toàn luồng), với các phương thức tạo và hủy được xác định và một vòng đời xác định khi nào và cách chúng được gọi và phương thức "cá thể" của nó có bảo vệ gói để nó thường được truy cập thông qua các đối tượng khác, không toàn cầu. Vẫn là một nguồn rắc rối tiềm năng, nhưng chắc chắn ít rắc rối hơn.

Đáng buồn thay, thay vì dạy các ví dụ tốt về cách làm Singletons. Chúng tôi dạy các ví dụ xấu, để các lập trình viên sử dụng chúng trong một thời gian, và sau đó nói với họ rằng họ là một mẫu thiết kế tồi.


23

Xem Wikipedia Singleton_potype

Nó cũng được coi là một mô hình chống đối bởi một số người, những người cảm thấy rằng nó được sử dụng quá mức, đưa ra những hạn chế không cần thiết trong các tình huống mà một trường hợp duy nhất của một lớp không thực sự cần thiết. [1] [2] [3] [4]

Tài liệu tham khảo (chỉ có tài liệu tham khảo liên quan từ bài viết)

  1. ^ Alex Miller. Mẫu tôi ghét # 1: Singleton , tháng 7 năm 2007
  2. ^ Scott Densmore. Tại sao singletons là ác , tháng 5 năm 2004
  3. ^ Steve Yegge. Singletons bị coi là ngu ngốc , tháng 9 năm 2004
  4. ^ JB Rainsberger, IBM. Sử dụng singletons của bạn một cách khôn ngoan , tháng 7 năm 2001

8
Một mô tả về mô hình không giải thích lý do tại sao nó được coi như ác ...
Jrgns

2
Khá công bằng: "Nó cũng được coi là một mô hình chống đối bởi một số người, những người cảm thấy rằng nó được sử dụng quá mức, đưa ra những hạn chế không cần thiết trong các tình huống mà một trường hợp duy nhất của một lớp không thực sự cần thiết." Nhìn vào các tài liệu tham khảo ... Dù sao cũng có RTFM với tôi.
GUI Junkie

17

Không phải bản thân singletons là xấu mà là mẫu thiết kế của GoF. Đối số thực sự duy nhất hợp lệ là mẫu thiết kế GoF không cho vay liên quan đến thử nghiệm, đặc biệt là nếu các thử nghiệm được chạy song song.

Sử dụng một thể hiện của một lớp là một cấu trúc hợp lệ miễn là bạn áp dụng các phương tiện sau trong mã:

  1. Hãy chắc chắn rằng lớp sẽ được sử dụng như một singleton thực hiện một giao diện. Điều này cho phép sơ khai hoặc giả được thực hiện bằng cùng một giao diện

  2. Hãy chắc chắn rằng Singleton là an toàn chủ đề. Đó là sự cống hiến.

  3. Các singleton nên đơn giản trong tự nhiên và không quá phức tạp.

  4. Trong thời gian chạy ứng dụng của bạn, trong đó các singletons cần được truyền đến một đối tượng nhất định, sử dụng một nhà máy lớp xây dựng đối tượng đó và để nhà máy lớp chuyển thể hiện singleton đến lớp cần nó.

  5. Trong quá trình thử nghiệm và để đảm bảo hành vi xác định, hãy tạo lớp singleton như một thể hiện riêng biệt như chính lớp thực tế hoặc một stub / mock thực hiện hành vi của nó và chuyển nó như là lớp yêu cầu. Đừng sử dụng yếu tố lớp tạo ra đối tượng đó trong bài kiểm tra cần đơn lẻ trong khi kiểm tra vì nó sẽ vượt qua thể hiện toàn cầu duy nhất của nó, điều này đánh bại mục đích.

Chúng tôi đã sử dụng Singletons trong các giải pháp của mình với rất nhiều thành công có thể kiểm chứng được đảm bảo hành vi xác định trong các luồng chạy thử nghiệm song song.


+1, Cuối cùng , một câu trả lời giải quyết khi một singleton có thể hợp lệ.
Pacerier

16

Tôi muốn giải quyết 4 điểm trong câu trả lời được chấp nhận, hy vọng ai đó có thể giải thích tại sao tôi sai.

  1. Tại sao ẩn phụ thuộc trong mã của bạn xấu? Đã có hàng tá các phụ thuộc ẩn (các cuộc gọi thời gian chạy C, các cuộc gọi API hệ điều hành, các cuộc gọi chức năng toàn cầu) và các phụ thuộc đơn lẻ rất dễ tìm thấy (tìm kiếm ví dụ ()).

    "Làm một cái gì đó toàn cầu để tránh vượt qua nó là một mùi mã." Tại sao không đi qua một cái gì đó xung quanh để tránh làm cho nó trở thành một mùi mã?

    Nếu bạn chuyển một đối tượng qua 10 chức năng trong ngăn xếp cuộc gọi chỉ để tránh một người độc thân, điều đó thật tuyệt phải không?

  2. Nguyên tắc trách nhiệm duy nhất: Tôi nghĩ rằng điều này hơi mơ hồ và phụ thuộc vào định nghĩa trách nhiệm của bạn. Một câu hỏi có liên quan sẽ là, tại sao việc thêm "trách nhiệm" cụ thể này vào một vấn đề của lớp?

  3. Tại sao việc truyền một đối tượng đến một lớp làm cho nó được liên kết chặt chẽ hơn so với việc sử dụng đối tượng đó như một đơn vị từ trong lớp?

  4. Tại sao nó thay đổi bao lâu nhà nước? Singletons có thể được tạo hoặc hủy bằng tay, do đó, điều khiển vẫn còn đó và bạn có thể làm cho thời gian tồn tại giống như thời gian tồn tại của một đối tượng không phải là người độc thân.

Về bài kiểm tra đơn vị:

  • không phải tất cả các lớp cần phải được kiểm tra đơn vị
  • không phải tất cả các lớp cần được kiểm tra đơn vị đều cần thay đổi việc thực hiện singleton
  • nếu họ làm cần thiết đơn vị thử nghiệm và làm cần phải thay đổi việc thực hiện, thật dễ dàng để thay đổi một lớp từ việc sử dụng một singleton để có singleton truyền cho nó qua dependency injection.

1
1. Tất cả những phụ thuộc ẩn? Những điều đó cũng xấu. Phụ thuộc ẩn luôn xấu xa. Nhưng trong trường hợp của CRT và HĐH, họ đã ở đó và họ là cách duy nhất để hoàn thành công việc. (Thử viết chương trình C mà không sử dụng thời gian chạy hoặc HĐH.) Khả năng đó để thực hiện công cụ vượt xa các tiêu cực của chúng. Singletons trong mã của chúng tôi không có được sự xa xỉ đó; vì chúng chắc chắn trong phạm vi kiểm soát và trách nhiệm của chúng tôi, mọi cách sử dụng (đọc: mọi phụ thuộc ẩn bổ sung) nên được coi là cách hợp lý duy nhất để hoàn thành công việc. Rất, rất ít trong số họ thực sự là.
cHao

2. "Trách nhiệm" thường được định nghĩa là "lý do để thay đổi". Một người độc thân kết thúc cả việc quản lý cuộc sống của mình và làm công việc thực sự của nó. Nếu bạn thay đổi cách thực hiện công việc hoặc cách xây dựng biểu đồ đối tượng, bạn phải thay đổi lớp. Nhưng nếu bạn có mã khác xây dựng biểu đồ đối tượng và lớp của bạn chỉ thực hiện công việc thực sự của nó, mã khởi tạo đó có thể thiết lập biểu đồ đối tượng theo cách nó thích. Mọi thứ đều có tính mô-đun và kiểm tra nhiều hơn, bởi vì bạn có thể chèn kiểm tra nhân đôi và xé mọi thứ theo ý muốn, và bạn không còn phụ thuộc vào các bộ phận chuyển động ẩn.
cHao

1
@cHao: 1. Thật tốt khi bạn nhận ra rằng "khả năng làm công cụ vượt xa những tiêu cực của họ." Một ví dụ sẽ là một khung đăng nhập. Thật là xấu khi bắt buộc phải vượt qua một đối tượng ghi nhật ký toàn cầu bằng cách tiêm phụ thuộc thông qua các lớp gọi hàm nếu điều đó có nghĩa là các nhà phát triển không khuyến khích đăng nhập. Do đó, singleton. 3. Điểm khớp nối chặt chẽ của bạn chỉ có liên quan nếu bạn muốn khách hàng của lớp kiểm soát hành vi thông qua đa hình. Đó thường không phải là trường hợp.
Jon

2
4. Tôi không chắc chắn rằng "không thể bị phá hủy bằng tay" là một hàm ý logic của "có thể chỉ là một trường hợp". Nếu đó là cách bạn định nghĩa singleton, thì chúng ta sẽ không nói về điều tương tự. Tất cả các lớp không nên được kiểm tra đơn vị. Đó là một quyết định kinh doanh và đôi khi chi phí phát triển theo thời gian hoặc thị trường sẽ được ưu tiên.
Jon

1
3. Nếu bạn không muốn đa hình, thì bạn thậm chí không cần một ví dụ. Bạn cũng có thể biến nó thành một lớp tĩnh, vì dù sao bạn cũng tự buộc mình vào một đối tượng và một triển khai duy nhất - và ít nhất thì API không nói dối bạn nhiều như vậy.
cHao

15

Vince Huston có những tiêu chí này, có vẻ hợp lý với tôi:

Singleton chỉ nên được xem xét nếu cả ba tiêu chí sau đây được thỏa mãn:

  • Quyền sở hữu của cá thể đơn lẻ không thể được chỉ định một cách hợp lý
  • Khởi tạo lười biếng là mong muốn
  • Truy cập toàn cầu không được quy định khác

Nếu quyền sở hữu của một thể hiện duy nhất, thời điểm và cách thức khởi tạo xảy ra và truy cập toàn cầu không phải là vấn đề, Singleton không đủ thú vị.


14

Singletons là xấu từ quan điểm thuần túy.

Từ quan điểm thực tế, singleton là một sự đánh đổi thời gian phát triển so với sự phức tạp .

Nếu bạn biết ứng dụng của bạn sẽ không thay đổi nhiều đến mức họ sẽ đồng ý. Chỉ cần biết rằng bạn có thể cần phải cấu trúc lại mọi thứ nếu yêu cầu của bạn thay đổi theo cách không mong muốn (điều này khá ổn trong hầu hết các trường hợp).

Singletons đôi khi cũng làm phức tạp thử nghiệm đơn vị .


3
Kinh nghiệm của tôi là bắt đầu với singletons sẽ thực sự làm bạn tổn thương ngay cả trong thời gian ngắn, không kể thời gian dài. Hiệu ứng này có thể giúp tôi giảm bớt vì vậy nếu ứng dụng đã tồn tại được một thời gian và có lẽ đã bị nhiễm các singletons khác. Bắt đầu từ số không? Tránh cho họ phải trả tất cả chi phí! Muốn có một ví dụ duy nhất của một đối tượng? Hãy để một nhà máy quản lý việc khởi tạo đối tượng này.
Sven

1
AFAIK Singleton một phương pháp nhà máy. Vì vậy, tôi không nhận được đề nghị của bạn.
tacone

3
"Một nhà máy"! = "Một nhà máy_method không thể tách rời khỏi các công cụ hữu ích khác trong cùng một lớp"
Sven

13

Không có gì sai với mẫu, giả sử nó đang được sử dụng cho một số khía cạnh của mô hình của bạn thực sự độc thân.

Tôi tin rằng phản ứng dữ dội là do sử dụng quá mức, đến lượt nó, là do thực tế đó là mô hình dễ hiểu và dễ thực hiện nhất.


6
Tôi nghĩ rằng phản ứng dữ dội có liên quan nhiều hơn đến những người thực hành thử nghiệm đơn vị chính thức nhận ra rằng họ là một cơn ác mộng để đối phó.
Jim Burger

1
Không chắc chắn tại sao điều này là -1. Đó không phải là câu trả lời mô tả nhất nhưng anh ấy cũng không sai.
Lập trình viên ngoài vòng pháp luật

13

Tôi sẽ không bình luận về tranh luận tốt / xấu, nhưng tôi đã không sử dụng chúng kể từ khi mùa xuân đến. Sử dụng tiêm phụ thuộc đã loại bỏ khá nhiều yêu cầu của tôi đối với các đơn vị, người phục vụ và nhà máy. Tôi thấy đây là một môi trường sạch và hiệu quả hơn nhiều, ít nhất là đối với loại công việc tôi làm (các ứng dụng web dựa trên Java).


3
Không phải là đậu mùa xuân, theo mặc định, Singletons?
mỏng

3
Vâng, nhưng ý tôi là những người độc thân 'mã hóa'. Tôi chưa 'viết một singleton' (nghĩa là với một nhà xây dựng tư nhân yada yada yada theo mẫu thiết kế) - nhưng vâng, với mùa xuân tôi đang sử dụng một ví dụ duy nhất.
prule

4
Vì vậy, khi những người khác làm điều đó, nó sẽ ổn (nếu tôi sẽ sử dụng nó sau), nhưng nếu những người khác làm điều đó và tôi sẽ không sử dụng nó, điều đó có xấu không?
Dainius

11

Singleton là một mẫu và có thể được sử dụng hoặc lạm dụng giống như bất kỳ công cụ nào khác.

Phần xấu của singleton nói chung là người dùng (hoặc tôi nên nói rằng việc sử dụng singleton không phù hợp cho những thứ mà nó không được thiết kế để làm). Người phạm tội lớn nhất đang sử dụng một singleton như một biến toàn cầu giả.


1
Cảm ơn Loki :) Chính xác là quan điểm của tôi. Toàn bộ cuộc săn phù thủy làm tôi nhớ đến cuộc tranh luận goto. Công cụ dành cho những người biết sử dụng chúng; sử dụng một công cụ mà bạn không thích có thể gây nguy hiểm cho bạn vì vậy hãy tránh nó nhưng KHÔNG bảo người khác không sử dụng / không học cách sử dụng nó đúng cách. Tôi không chính xác là "pro-singleton" nhưng tôi thích thực tế là tôi có công cụ như vậy và tôi có thể cảm thấy nơi nào phù hợp để sử dụng nó. Giai đoạn = Stage.
dkellner

8

Khi bạn viết mã bằng singletons, giả sử, một logger hoặc kết nối cơ sở dữ liệu và sau đó bạn phát hiện ra bạn cần nhiều hơn một nhật ký hoặc nhiều hơn một cơ sở dữ liệu, bạn sẽ gặp rắc rối.

Singletons làm cho rất khó để di chuyển từ chúng sang các đối tượng thông thường.

Ngoài ra, quá dễ dàng để viết một singleton không an toàn chủ đề.

Thay vì sử dụng singletons, bạn nên chuyển tất cả các đối tượng tiện ích cần thiết từ chức năng này sang chức năng khác. Điều đó có thể được đơn giản hóa nếu bạn bọc tất cả chúng vào một đối tượng trợ giúp, như thế này:

void some_class::some_function(parameters, service_provider& srv)
{
    srv.get<error_logger>().log("Hi there!");
    this->another_function(some_other_parameters, srv);
}

4
đối số truyền cho mọi phương thức là tuyệt vời, nó nên đi đến ít nhất 10 cách hàng đầu để làm ô nhiễm api của bạn.
Dainius

Đó là một nhược điểm rõ ràng và một ngôn ngữ nên được xử lý bằng ngôn ngữ, bằng các biện pháp bằng cách nào đó truyền vào các cuộc gọi lồng nhau, nhưng không có sự hỗ trợ như vậy, nó phải được thực hiện thủ công. Hãy xem xét rằng ngay cả các singletons tự nhiên, giống như một đối tượng đại diện cho đồng hồ hệ thống, có khả năng gặp rắc rối. Ví dụ, làm thế nào bạn sẽ bao gồm mã phụ thuộc đồng hồ (nghĩ nhiều đồng hồ điện thuế) với các thử nghiệm?
Roman Odaisky

phụ thuộc vào những gì bạn đang kiểm tra, nếu bạn không kiểm tra singleton, tại sao bạn quan tâm đến cách hoạt động của nó, nếu 2 đối tượng ở xa của bạn phụ thuộc vào hành vi của nhau, thì đó không phải là vấn đề đơn lẻ ..
Dainius

Bạn có thể cung cấp ngôn ngữ nào sẽ truyền bá thành các cuộc gọi lồng nhau?
Dainius

1
Nếu bạn có một mô-đun phụ thuộc vào mô-đun khác và bạn không biết / không thể tạo ra mô-đun đó, bạn sẽ gặp khó khăn cho dù đó là đơn lẻ hay không. Mọi người đang sử dụng cách thừa kế thường xuyên, nơi họ nên sử dụng bố cục, nhưng bạn không nói rằng thừa kế là xấu và OOP nên tránh bằng mọi giá, bởi vì rất nhiều người đang mắc lỗi thiết kế ở đó, hay bạn?
Dainius

8

Các vấn đề với singletons là vấn đề tăng phạm vi và do đó khớp nối . Không thể phủ nhận rằng có một số tình huống mà bạn cần truy cập vào một trường hợp duy nhất, và nó có thể được thực hiện theo những cách khác.

Bây giờ tôi thích thiết kế xung quanh một container đảo ngược điều khiển (IoC) và cho phép các vòng đời được điều khiển bởi container. Điều này cung cấp cho bạn lợi ích của các lớp phụ thuộc vào thể hiện để không nhận thức được thực tế rằng có một thể hiện duy nhất. Tuổi thọ của singleton có thể được thay đổi trong tương lai. Một ví dụ như tôi gặp gần đây là một điều chỉnh dễ dàng từ đơn luồng sang đa luồng.

FWIW, nếu đó là PIA khi bạn cố gắng kiểm tra đơn vị thì nó sẽ thành PIA khi bạn cố gắng gỡ lỗi, sửa lỗi hoặc nâng cao nó.



7

Singletons KHÔNG xấu. Nó chỉ tệ khi bạn tạo ra thứ gì đó độc nhất toàn cầu mà không phải là duy nhất trên toàn cầu.

Tuy nhiên, có "dịch vụ phạm vi ứng dụng" (nghĩ về một hệ thống nhắn tin làm cho các thành phần tương tác) - CUỘC GỌI này cho một người độc thân, một "MessageQueue" - lớp có phương thức "SendMessage (...)".

Sau đó, bạn có thể thực hiện các thao tác sau từ khắp mọi nơi:

MessageQueue.C hiện.SendMessage (MailArrivedMessage mới (...));

Và, tất nhiên, làm:

MessageQueue.C Hiện.RegisterReceiver (này);

trong các lớp thực hiện IMessageReceiver.


6
Và nếu tôi muốn tạo một hàng đợi tin nhắn thứ hai, với phạm vi nhỏ hơn, tại sao tôi không được phép sử dụng lại mã của bạn để tạo nó? Một singleton ngăn chặn điều đó. Nhưng nếu bạn chỉ tạo một lớp hàng đợi tin nhắn thông thường, và sau đó tạo một thể hiện toàn cầu để hoạt động như một "phạm vi ứng dụng", tôi có thể tạo một thể hiện thứ hai cho mục đích sử dụng khác. Nhưng nếu bạn biến lớp thành một singleton, tôi sẽ phải viết một lớp xếp hàng tin nhắn thứ hai .
jalf

1
Ngoài ra, tại sao chúng ta không nên có một Danh sách khách hàng toàn cầu để chúng ta có thể gọi nó một cách độc đáo từ bất cứ nơi nào như MessageQueue? Tôi tin rằng câu trả lời là giống nhau cho cả hai: Nó thực sự tạo ra một biến toàn cầu.
Truyền thuyết Bước sóng

7

Quá nhiều người đặt các đối tượng không an toàn trong một mẫu đơn. Tôi đã thấy các ví dụ về DataContext ( LINQ to SQL ) được thực hiện theo mẫu đơn, mặc dù thực tế là DataContext không an toàn cho luồng và hoàn toàn là một đối tượng đơn vị công việc.


Nhiều người viết mã đa luồng không an toàn, điều đó có nghĩa là chúng ta nên loại bỏ các luồng?
Dainius

@Dainius: Trong thực tế, có. IMO mô hình đồng thời mặc định phải là đa tiến trình, với cách để hai quá trình (1) truyền tin nhắn cho nhau một cách dễ dàng và / hoặc (2) chia sẻ bộ nhớ trên cơ sở khi cần thiết. Threading rất hữu ích nếu bạn muốn chia sẻ mọi thứ , nhưng bạn không bao giờ thực sự muốn điều đó. Nó có thể được mô phỏng bằng cách chia sẻ toàn bộ không gian địa chỉ, nhưng đó cũng sẽ được coi là một mô hình chống.
cHao

@Dainius: Và tất nhiên, điều đó cho rằng đồng thời trung thực với lòng tốt là cần thiết. Thông thường tất cả những gì bạn muốn là có thể làm một việc trong khi bạn chờ HĐH làm việc khác. (Chẳng hạn, cập nhật giao diện người dùng trong khi đọc từ ổ cắm.) API async phong nha có thể khiến luồng toàn bộ không cần thiết trong phần lớn các trường hợp.
cHao

Vì vậy, nếu bạn có ma trận dữ liệu khổng lồ, bạn cần xử lý, tốt hơn là thực hiện điều đó trong một luồng, bởi vì nhiều người có thể làm sai ..
Dainius

@Dainius: Trong nhiều trường hợp, vâng. (Việc phân luồng thực sự có thể làm bạn chậm lại , nếu bạn thêm đồng bộ hóa vào hỗn hợp. Đây là một ví dụ điển hình về lý do đa luồng không phải là suy nghĩ đầu tiên của mọi người.) Trong các trường hợp khác, tốt hơn là nên có hai quy trình chỉ chia sẻ Những thứ cần thiết. Tất nhiên, bạn phải sắp xếp các quy trình để chia sẻ bộ nhớ. Nhưng thành thật mà nói, tôi thấy đó là một điều tốt - ít nhất là tốt hơn nhiều so với mặc định ở chế độ chia sẻ mọi thứ. Nó đòi hỏi bạn phải nói rõ ràng (và vì vậy, lý tưởng nhất là phải biết) phần nào được chia sẻ, và do đó phần nào cần phải an toàn cho chuỗi.
cHao

6

Đây là một điều nữa về singletons mà chưa ai nói.

Trong hầu hết các trường hợp, "singletonity" là một chi tiết triển khai cho một số lớp hơn là đặc trưng của giao diện của nó. Đảo ngược Container điều khiển có thể ẩn đặc tính này khỏi người dùng lớp; bạn chỉ cần đánh dấu lớp của mình là một singleton ( @Singletonví dụ với chú thích bằng Java) và đó là nó; IoCC sẽ làm phần còn lại. Bạn không cần cung cấp quyền truy cập toàn cầu vào cá thể đơn lẻ của mình vì quyền truy cập đã được IoCC quản lý. Do đó, không có gì sai với IoC Singletons.

GoF Singletons đối diện với IoC Singletons được cho là phơi bày "tính đơn lẻ" trong giao diện thông qua phương thức getInstance () và do đó họ phải chịu đựng mọi thứ đã nói ở trên.


3
Theo tôi, "đơn lẻ" là một chi tiết môi trường thời gian chạy, và không nên xem xét bởi lập trình viên đã viết mã lớp. Thay vào đó, nó là một sự cân nhắc được thực hiện bởi lớp NGƯỜI DÙNG. chỉ có NGƯỜI DÙNG biết có bao nhiêu trường hợp thực sự được yêu cầu.
Động cơ Trái đất

5

Singletons không xấu, nếu bạn sử dụng nó đúng cách & tối thiểu . Có rất nhiều mẫu thiết kế tốt khác thay thế cho nhu cầu của singleton tại một số điểm (& cũng cho kết quả tốt nhất). Nhưng một số lập trình viên không biết về những mô hình tốt đó và sử dụng singleton cho tất cả các trường hợp làm cho singleton trở nên xấu xa đối với họ.


Tuyệt vời! Hoàn toàn đồng ý! Nhưng bạn có thể, và có lẽ nên, giải thích nhiều hơn nữa. Chẳng hạn như mở rộng các mẫu thiết kế thường bị bỏ qua nhất và làm thế nào để "sử dụng đúng cách và tối thiểu" sử dụng singletons. Nói thì dễ hơn làm ! : P
cregox

Về cơ bản, phương án thay thế là truyền các đối tượng xung quanh thông qua các tham số phương thức, thay vì truy cập chúng thông qua trạng thái toàn cầu.
Truyền thuyết Chiều dài

5

Đầu tiên một lớp và các cộng tác viên của nó trước tiên nên thực hiện mục đích dự định của họ hơn là tập trung vào những người phụ thuộc. Quản lý vòng đời (khi các thể hiện được tạo ra và khi chúng đi ra khỏi phạm vi) không nên là một phần của trách nhiệm của các lớp. Cách thực hành tốt nhất được chấp nhận cho việc này là chế tạo hoặc cấu hình một thành phần mới để quản lý các phụ thuộc bằng cách sử dụng phép nội xạ phụ thuộc.

Thông thường phần mềm trở nên phức tạp hơn, có nhiều trường hợp độc lập của lớp Singleton với trạng thái khác nhau. Cam kết mã chỉ đơn giản là lấy singleton là sai trong những trường hợp như vậy. Việc sử dụng Singleton.getInstance()có thể ổn đối với các hệ thống đơn giản nhỏ nhưng nó không hoạt động / quy mô khi người ta có thể cần một phiên bản khác của cùng một lớp.

Không có lớp nào nên được coi là một đơn lẻ mà thay vào đó nên là một ứng dụng của việc sử dụng nó hoặc cách nó được sử dụng để cấu hình người phụ thuộc. Đối với một cách nhanh chóng và khó chịu, điều này không thành vấn đề - chỉ cần mã hóa cứng cho biết đường dẫn tệp không quan trọng nhưng đối với các ứng dụng lớn hơn, các phụ thuộc như vậy cần được xử lý và quản lý theo cách phù hợp hơn bằng cách sử dụng DI.

Các vấn đề mà singleton gây ra trong thử nghiệm là một triệu chứng của trường hợp / môi trường sử dụng đơn được mã hóa cứng của họ. Bộ kiểm thử và nhiều bài kiểm tra là mỗi cá nhân và tách biệt một thứ không tương thích với mã hóa cứng một đơn.


4

Vì về cơ bản chúng là các biến toàn cục hướng đối tượng, bạn thường có thể thiết kế các lớp của mình theo cách sao cho bạn không cần chúng.


Nếu lớp của bạn không bị giới hạn một lần, bạn sẽ cần các thành viên tĩnh trong lớp của bạn được quản lý bởi các semaphores xuất hiện khá nhiều điều tương tự! Đề xuất thay thế của bạn là gì?
Jeach

Tôi có một ứng dụng khổng lồ với "MainScreen" màn hình này mở ra nhiều biểu mẫu cửa sổ / giao diện người dùng phương thức / không theo phương thức nhỏ hơn. IMHO Tôi cảm thấy MainScreen phải là một singleton để ví dụ, một widget ở một góc xa của ứng dụng muốn hiển thị trạng thái của nó trong thanh trạng thái của MainScreen, tất cả những gì nó phải làm là MainScreen.getInstance (). SetStatus ( "Một số tiếp theo"); Bạn đề xuất gì thay thế? Vượt qua MainScreen trên tất cả các ứng dụng ?? : D
Salvin Francis

2
@SalvinFrancis: Những gì tôi muốn giới thiệu là, bạn ngừng có những đối tượng quan tâm đến những thứ họ không nên quan tâm và lén lút trên ứng dụng để gây rối với nhau. :) Ví dụ của bạn sẽ được thực hiện tốt hơn nhiều với các sự kiện. Khi bạn thực hiện các sự kiện một cách chính xác, một widget thậm chí không cần quan tâm liệu có MainScreen hay không; nó chỉ phát "hey, chuyện đã xảy ra" và bất cứ điều gì đã đăng ký vào sự kiện "chuyện xảy ra" (có thể là MainScreen, WidgetTest hoặc một cái gì đó hoàn toàn khác!) quyết định cách nó muốn phản hồi. Đó là cách OOP đáng lẽ phải được thực hiện bằng mọi cách. :)
cHao

1
@Salvin Hãy xem xét mức độ khó của lý do về MainScreen khi gỡ lỗi nếu nó được 'âm thầm' cập nhật bởi nhiều thành phần. Ví dụ của bạn là một lý do hoàn hảo tại sao singletons là xấu.
Truyền thuyết Bước sóng
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.