Một số người cho rằng Mẫu Singleton luôn là mẫu chống. Bạn nghĩ sao?
Một số người cho rằng Mẫu Singleton luôn là mẫu chống. Bạn nghĩ sao?
Câu trả lời:
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:
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.
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.
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ọ.
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ó.
Độ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.
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ị.
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.
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 đó.
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ó.
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ì 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.
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.