Khi nào thì Singleton thích hợp? [đóng cửa]


Câu trả lời:


32

Hai lời chỉ trích chính của Singletons rơi vào hai phe từ những gì tôi đã quan sát:

  1. Singletons bị lạm dụng và lạm dụng bởi các lập trình viên có khả năng kém hơn và vì vậy mọi thứ trở thành một singleton và bạn thấy mã bị lấp đầy với các tham chiếu Class :: get_instance (). Nói chung, chỉ có một hoặc hai tài nguyên (ví dụ như kết nối cơ sở dữ liệu) đủ điều kiện để sử dụng mẫu Singleton.
  2. Singletons về cơ bản là các lớp tĩnh, dựa trên một hoặc nhiều phương thức và thuộc tính tĩnh. Tất cả mọi thứ tĩnh hiện tại thực, các vấn đề hữu hình khi bạn cố gắng thực hiện Kiểm tra đơn vị vì chúng đại diện cho các ngõ cụt trong mã của bạn không thể bị chế giễu hoặc sơ khai. Kết quả là, khi bạn kiểm tra một lớp dựa trên Singleton (hoặc bất kỳ phương thức hoặc lớp tĩnh nào khác), bạn không chỉ kiểm tra lớp đó mà còn cả phương thức hoặc lớp tĩnh.

Kết quả của cả hai điều này, một cách tiếp cận phổ biến là sử dụng tạo một đối tượng container rộng để chứa một thể hiện của các lớp này và chỉ đối tượng container sửa đổi các loại lớp này trong khi nhiều lớp khác có thể được cấp quyền truy cập vào chúng để sử dụng từ đối tượng container.


6
Tôi chỉ muốn nói rằng bạn có thể mô phỏng các phương thức tĩnh trong Java bằng JMockit ( code.google.com/p/jmockit ). Đây là một công cụ rất tiện dụng.
Michael K

3
Một số suy nghĩ: container là một Singleton, vì vậy bạn chưa thoát khỏi Singletons. Ngoài ra, nếu bạn đang viết thư viện bằng API công khai, bạn không thể buộc người dùng API sử dụng bộ chứa của mình . Nếu bạn cần một người quản lý tài nguyên toàn cầu trong thư viện của mình (ví dụ: bảo vệ quyền truy cập vào một tài nguyên vật lý) thì bạn cần sử dụng Singleton.
Scott Whitlock

2
@Scott Whitlock tại sao nó cần phải là một singleton? Chỉ vì chỉ có một trường hợp duy nhất không làm cho nó như vậy. Bất kỳ thư viện IoC nào cũng sẽ quản lý loại phụ thuộc này ...
MattDavey

Giải pháp này đề xuất nó còn được gọi là Hộp công cụ
cregox

41

Tôi đồng ý rằng đó là một mô hình chống. Tại sao? Bởi vì nó cho phép mã của bạn nói dối về các phụ thuộc của nó và bạn không thể tin tưởng các lập trình viên khác không đưa ra trạng thái có thể thay đổi trong các singletons bất biến trước đây của bạn.

Một lớp có thể có một hàm tạo chỉ lấy một chuỗi, vì vậy bạn nghĩ rằng nó được khởi tạo một cách cô lập và không có tác dụng phụ. Tuy nhiên, âm thầm, nó đang liên lạc với một số đối tượng singleton công khai, có sẵn trên toàn cầu, để bất cứ khi nào bạn khởi tạo lớp, nó chứa dữ liệu khác nhau. Đây là một vấn đề lớn, không chỉ đối với người dùng API của bạn, mà còn về khả năng kiểm tra mã. Để kiểm tra đơn vị mã đúng cách, bạn cần quản lý vi mô và nhận thức được trạng thái toàn cầu trong đơn, để có kết quả kiểm tra nhất quán.


Chúc tôi có thể nâng cao hơn một lần!
MattDavey

34

Mẫu Singleton về cơ bản chỉ là một biến toàn cục được khởi tạo một cách lười biếng. Các biến toàn cầu nói chung và được coi là xấu xa vì chúng cho phép hành động ma quái ở khoảng cách giữa các phần dường như không liên quan của một chương trình. Tuy nhiên, IMHO không có gì sai với các biến toàn cục được đặt một lần, từ một nơi, như một phần của thói quen khởi tạo chương trình (ví dụ: bằng cách đọc tệp cấu hình hoặc đối số dòng lệnh) và được coi là hằng số sau đó. Việc sử dụng các biến toàn cục như vậy chỉ khác nhau về chữ cái, không phải về tinh thần, với việc có một hằng số được đặt tên được khai báo tại thời điểm biên dịch.

Tương tự như vậy, ý kiến ​​của tôi về Singletons là chúng rất tệ nếu và chỉ khi chúng được sử dụng để vượt qua trạng thái có thể thay đổi giữa các phần dường như không liên quan đến chương trình. Nếu chúng không chứa trạng thái có thể thay đổi hoặc nếu trạng thái có thể thay đổi mà chúng chứa hoàn toàn được đóng gói để người dùng của đối tượng không phải biết về nó ngay cả trong môi trường đa luồng, thì không có gì sai với họ.


3
Vì vậy, nói cách khác, bạn chống lại bất kỳ việc sử dụng cơ sở dữ liệu trong một chương trình.
Kendall Helmstetter Gelner

Có một video nói chuyện về công nghệ google nổi tiếng phản ánh ý kiến ​​của bạn youtube.com/watch?v=-FRm3VPhseI
Martin York

2
@KendallHelmstetterGelner: Có sự khác biệt giữa lưu trữ trạng thái và lưu trữ thứ cấp. Không chắc bạn có thể giữ toàn bộ DB trong bộ nhớ.
Martin York

7

Tại sao mọi người sử dụng nó?

Tôi đã thấy khá nhiều singletons trong thế giới PHP. Tôi không nhớ bất kỳ trường hợp sử dụng nào mà tôi thấy mẫu này là hợp lý. Nhưng tôi nghĩ rằng tôi đã có một ý tưởng về động lực tại sao mọi người đã sử dụng nó.

  1. Độc dụ .

    "Sử dụng một ví dụ duy nhất của lớp C trong suốt ứng dụng."

    Đây là một yêu cầu hợp lý, ví dụ như đối với "kết nối cơ sở dữ liệu mặc định". Điều đó không có nghĩa là bạn sẽ không bao giờ tạo kết nối db thứ hai, nó chỉ có nghĩa là bạn thường làm việc với kết nối mặc định.

  2. Khởi tạo đơn .

    "Không cho phép lớp C được khởi tạo nhiều lần (mỗi quy trình, mỗi yêu cầu, v.v.)."

    Điều này chỉ có liên quan nếu việc khởi tạo lớp sẽ có tác dụng phụ xung đột với các trường hợp khác.

    Thường thì những xung đột này có thể tránh được bằng cách thiết kế lại thành phần - ví dụ bằng cách loại bỏ các tác dụng phụ khỏi hàm tạo của lớp. Hoặc họ có thể được giải quyết theo những cách khác. Nhưng vẫn có thể có một số trường hợp sử dụng hợp pháp.

    Bạn cũng nên suy nghĩ về việc liệu yêu cầu "duy nhất" có thực sự có nghĩa là "một yêu cầu cho mỗi quy trình" hay không. Ví dụ, đối với tài nguyên đồng thời, yêu cầu là "một trên toàn bộ hệ thống, trên các quy trình" chứ không phải "một cho mỗi quy trình". Và đối với những thứ khác, nó thay vì "bối cảnh ứng dụng", và bạn tình cờ có một bối cảnh ứng dụng cho mỗi quy trình.

    Mặt khác, không cần phải thực thi giả định này. Thực thi điều này cũng có nghĩa là bạn không thể tạo một thể hiện riêng cho các bài kiểm tra đơn vị.

  3. Truy cập toàn cầu.

    Điều này chỉ hợp pháp nếu bạn không có cơ sở hạ tầng thích hợp để chuyển các đối tượng xung quanh đến nơi chúng được sử dụng. Điều này có thể có nghĩa là khuôn khổ hoặc môi trường của bạn tệ, nhưng nó có thể không nằm trong khả năng của bạn để khắc phục điều đó.

    Giá cả là khớp nối chặt chẽ, phụ thuộc ẩn, và tất cả mọi thứ xấu về nhà nước toàn cầu. Nhưng có lẽ bạn đã chịu đựng những điều này.

  4. Lười biếng ngay lập tức.

    Đây không phải là một phần cần thiết của một singleton, nhưng có vẻ như cách phổ biến nhất để thực hiện chúng. Nhưng, trong khi khởi tạo lười biếng là một điều tốt đẹp để có, bạn không thực sự cần một người độc thân để đạt được điều đó.

Thực hiện điển hình

Việc triển khai điển hình là một lớp với hàm tạo riêng và biến đối tượng tĩnh và phương thức getInstance () tĩnh với khởi tạo lười biếng.

Ngoài các vấn đề được đề cập ở trên, điều này cắn với nguyên tắc trách nhiệm duy nhất , bởi vì lớp này kiểm soát việc khởi tạo và vòng đời của chính nó , ngoài các trách nhiệm khác mà lớp đã có.

Phần kết luận

Trong nhiều trường hợp, bạn có thể đạt được kết quả tương tự mà không cần đơn lẻ và không có trạng thái toàn cầu. Thay vào đó, bạn nên sử dụng tiêm phụ thuộc và bạn có thể muốn xem xét một hộp tiêm phụ thuộc .

Tuy nhiên, có những trường hợp sử dụng mà bạn còn các yêu cầu hợp lệ sau:

  • Ví dụ đơn (nhưng không phải là khởi tạo đơn)
  • Truy cập toàn cầu (vì bạn đang làm việc với khung tuổi đồng)
  • Lười ngay lập tức (vì nó chỉ là tốt đẹp để có)

Vì vậy, đây là những gì bạn có thể làm trong trường hợp này:

  • Tạo một lớp C mà bạn muốn khởi tạo, với một hàm tạo công khai.

  • Tạo một lớp S riêng biệt với một biến đối tượng tĩnh và phương thức S :: getInstance () tĩnh với khởi tạo lười biếng, sẽ sử dụng lớp C làm ví dụ.

  • Loại bỏ tất cả các tác dụng phụ khỏi hàm tạo của C. Thay vào đó, hãy đặt các tác dụng phụ này vào phương thức S :: getInstance ().

  • Nếu bạn có nhiều hơn một lớp với các yêu cầu trên, bạn có thể xem xét để quản lý các thể hiện của lớp với một thùng chứa dịch vụ cục bộ nhỏ và chỉ sử dụng thể hiện tĩnh cho vùng chứa. Vì vậy, S :: getContainer () sẽ cung cấp cho bạn một thùng chứa dịch vụ ngay lập tức và bạn lấy các đối tượng khác từ vùng chứa.

  • Tránh gọi hàm getInstance () tĩnh nơi bạn có thể. Sử dụng tiêm phụ thuộc thay thế, bất cứ khi nào có thể. Đặc biệt, nếu bạn sử dụng cách tiếp cận vùng chứa với nhiều đối tượng phụ thuộc lẫn nhau, thì không ai trong số này phải gọi S :: getContainer ().

  • Tùy chọn tạo một giao diện mà lớp C thực hiện và sử dụng giao diện này để ghi lại giá trị trả về của S :: getInstance ().

(Chúng ta vẫn gọi đây là một singleton chứ? Tôi để phần này vào phần bình luận ..)

Những lợi ích:

  • Bạn có thể tạo một phiên bản C riêng để thử nghiệm đơn vị mà không cần chạm vào bất kỳ trạng thái toàn cầu nào.

  • Quản lý sơ thẩm được tách ra khỏi chính lớp -> tách các mối quan tâm, nguyên tắc trách nhiệm duy nhất.

  • Sẽ khá dễ dàng khi để S :: getInstance () sử dụng một lớp khác cho ví dụ hoặc thậm chí tự động xác định lớp nào sẽ sử dụng.


1

Cá nhân tôi sẽ sử dụng singletons khi tôi cần 1, 2 hoặc 3 hoặc một số lượng hạn chế của các đối tượng cho lớp cụ thể trong câu hỏi. Hoặc tôi muốn truyền đạt tới người dùng của lớp tôi rằng tôi không muốn nhiều trường hợp của lớp tôi được tạo để nó hoạt động đúng.

Ngoài ra, tôi sẽ chỉ sử dụng nó khi tôi cần sử dụng nó ở hầu hết mọi nơi trong mã của mình và tôi không muốn truyền một đối tượng làm tham số cho mỗi lớp hoặc hàm cần nó.

Ngoài ra, tôi sẽ chỉ sử dụng một singleton nếu nó không phá vỡ tính minh bạch tham chiếu của chức năng khác. Có nghĩa là cho một số đầu vào, nó sẽ luôn tạo ra cùng một đầu ra. Tức là tôi không sử dụng nó cho nhà nước toàn cầu. Trừ khi có thể nhà nước toàn cầu được khởi tạo một lần và không bao giờ thay đổi.

Còn khi không sử dụng, hãy xem 3 điều trên và thay đổi chúng thành ngược lại.


3
-0 nhưng tôi gần -1. Tôi thà vượt qua nó ở mọi nơi trong mã của mình thay vì sử dụng BUT singleton nếu tôi thực sự lười biếng, tôi có thể làm điều đó. Tôi sẽ sử dụng các vars toàn cầu thay vì nếu tôi có thể hoặc các thành viên tĩnh. vars toàn cầu thực sự được ưa thích hơn nhiều (với tôi) sau đó là singletons. Globals đừng nói dối
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.