it-swarm-vi.com

Khi nào thì Singleton thích hợp?

Một số người cho rằng Mẫu đơn luôn là mẫu chống. Bạn nghĩ sao?

67
Fishtoaster

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.

32
Noah Goodrich

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 sẽ 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ã chính xác, 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.

42
Magnus Wolffelt

Mẫu Singleton về cơ bản chỉ là một biến toàn cục khởi tạo 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 trong 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ọ.

34
dsimcha

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 khi tôi tìm thấy mô hình hợp lý. Nhưng tôi nghĩ tôi có một ý tưởng về động lực tại sao mọi người đã sử dụng nó.

  1. Độc thân 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ụ: cho "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. Độc thân tức thời.

    "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ụ từ trình xây dựng 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 đồng thời tài nguyên, 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 trên 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. Khởi tạo lười biếng.

    Đâ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ần 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 một hàm tạo riêng và một biến đối tượng tĩnh và một 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 , vì lớp này thực hiện 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 phương pháp tiêm phụ thuộc và bạn có thể muốn xem xét hộp chứa phụ thuộc tiêm .

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 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.

7
donquixote

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à với một số đầu vào, nó sẽ luôn tạo ra cùng một đầu ra. I E. 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.

1
Brian R. Bondy