Biến toàn cầu được tôn vinh - trở thành một lớp toàn cầu được tôn vinh. Một số người nói phá vỡ thiết kế hướng đối tượng.
Cung cấp cho tôi các kịch bản, ngoài logger cũ tốt, nơi sử dụng singleton có ý nghĩa.
Biến toàn cầu được tôn vinh - trở thành một lớp toàn cầu được tôn vinh. Một số người nói phá vỡ thiết kế hướng đối tượng.
Cung cấp cho tôi các kịch bản, ngoài logger cũ tốt, nơi sử dụng singleton có ý nghĩa.
Câu trả lời:
Trong hành trình tìm kiếm sự thật, tôi phát hiện ra rằng thực sự có rất ít lý do "chấp nhận được" để sử dụng Singleton.
Một lý do có xu hướng xuất hiện lặp đi lặp lại trên mạng quốc tế là vì lớp "ghi nhật ký" (mà bạn đã đề cập). Trong trường hợp này, một Singleton có thể được sử dụng thay vì một thể hiện của một lớp vì một lớp ghi nhật ký thường cần được sử dụng lặp đi lặp lại nhiều lần bởi mỗi lớp trong một dự án. Nếu mỗi lớp sử dụng lớp ghi nhật ký này, việc tiêm phụ thuộc sẽ trở nên cồng kềnh.
Ghi nhật ký là một ví dụ cụ thể của Singleton "chấp nhận được" vì nó không ảnh hưởng đến việc thực thi mã của bạn. Vô hiệu hóa đăng nhập, thực thi mã vẫn như cũ. Cho phép nó, giống nhau. Misko đưa nó theo cách sau trong Root Nguyên nhân của Singletons , "Thông tin ở đây chảy theo một cách: Từ ứng dụng của bạn vào logger. Mặc dù logger là trạng thái toàn cầu, vì không có thông tin nào chuyển từ logger vào ứng dụng của bạn, logger có thể chấp nhận được."
Tôi chắc chắn có những lý do hợp lệ khác là tốt. Alex Miller, trong " Mẫu tôi ghét ", nói về người định vị dịch vụ và giao diện người dùng phía khách hàng cũng có thể là những lựa chọn "có thể chấp nhận".
Đọc thêm tại Singleton Tôi yêu bạn, nhưng bạn đang làm tôi thất vọng.
Một ứng cử viên Singleton phải đáp ứng ba yêu cầu:
Nếu Singleton đề xuất của bạn chỉ có một hoặc hai trong số các yêu cầu này, thiết kế lại gần như luôn luôn là lựa chọn chính xác.
Ví dụ, bộ đệm máy in không có khả năng được gọi từ nhiều nơi (menu In), vì vậy bạn có thể sử dụng mutexes để giải quyết vấn đề truy cập đồng thời.
Một logger đơn giản là ví dụ rõ ràng nhất về Singleton có thể hợp lệ, nhưng điều này có thể thay đổi với các sơ đồ ghi nhật ký phức tạp hơn.
Đọc các tệp cấu hình chỉ nên đọc khi khởi động và gói chúng trong Singleton.
Properties.Settings.Default
trong .NET.
Bạn sử dụng một singleton khi bạn cần quản lý tài nguyên được chia sẻ. Ví dụ, một bộ đệm máy in. Ứng dụng của bạn chỉ nên có một phiên bản duy nhất của bộ đệm để tránh yêu cầu xung đột cho cùng một tài nguyên.
Hoặc kết nối cơ sở dữ liệu hoặc trình quản lý tệp, v.v.
Chỉ đọc các singletons lưu trữ một số trạng thái toàn cầu (ngôn ngữ người dùng, filepath trợ giúp, đường dẫn ứng dụng) là hợp lý. Hãy cẩn thận khi sử dụng singletons để kiểm soát logic kinh doanh - đơn lẻ hầu như luôn luôn là nhiều
Quản lý kết nối (hoặc nhóm kết nối) với cơ sở dữ liệu.
Tôi cũng sẽ sử dụng nó để lấy và lưu trữ thông tin trên các tập tin cấu hình bên ngoài.
Một trong những cách bạn sử dụng một singleton là bao gồm một trường hợp trong đó phải có một "nhà môi giới" duy nhất kiểm soát quyền truy cập vào tài nguyên. Singletons là tốt trong logger bởi vì họ môi giới truy cập vào, nói, một tập tin, chỉ có thể được viết riêng. Đối với một cái gì đó như đăng nhập, họ cung cấp một cách trừu tượng hóa việc ghi vào một cái gì đó giống như một tệp nhật ký - bạn có thể gói một cơ chế bộ đệm vào đĩa đơn của mình, v.v ...
Cũng nghĩ về một tình huống mà bạn có một ứng dụng có nhiều cửa sổ / luồng / vv, nhưng cần một điểm giao tiếp duy nhất. Tôi đã từng sử dụng một để kiểm soát các công việc mà tôi muốn ứng dụng của mình khởi chạy. Người độc thân chịu trách nhiệm tuần tự hóa các công việc và hiển thị trạng thái của họ cho bất kỳ phần nào khác của chương trình mà họ quan tâm. Trong loại kịch bản này, bạn có thể xem một singleton giống như một lớp "máy chủ" đang chạy bên trong ứng dụng của bạn ... HTH
Một singleton nên được sử dụng khi quản lý quyền truy cập vào một tài nguyên được chia sẻ bởi toàn bộ ứng dụng và nó sẽ bị phá hủy khi có nhiều phiên bản của cùng một lớp. Đảm bảo rằng quyền truy cập vào luồng tài nguyên được chia sẻ an toàn là một ví dụ rất hay về việc loại mẫu này có thể là quan trọng.
Khi sử dụng Singletons, bạn nên đảm bảo rằng bạn không vô tình che giấu sự phụ thuộc. Lý tưởng nhất là các singletons (giống như hầu hết các biến tĩnh trong ứng dụng) được thiết lập trong quá trình thực thi mã khởi tạo của bạn cho ứng dụng (static void Main () cho các tệp thực thi C #, static void main () cho các tệp thực thi java) và sau đó được truyền vào tất cả các lớp khác được khởi tạo mà yêu cầu nó. Điều này giúp bạn duy trì khả năng kiểm tra.
Một ví dụ thực tế về một singleton có thể được tìm thấy trong Test :: Builder , lớp chỉ hỗ trợ cho mọi mô-đun thử nghiệm Perl hiện đại. Test :: Builder singleton lưu trữ và môi giới trạng thái và lịch sử của quá trình thử nghiệm (kết quả thử nghiệm lịch sử, đếm số lần chạy thử) cũng như những thứ như nơi đầu ra thử nghiệm đang diễn ra. Đây là tất cả cần thiết để phối hợp nhiều mô-đun thử nghiệm, được viết bởi các tác giả khác nhau, để làm việc cùng nhau trong một kịch bản thử nghiệm duy nhất.
Lịch sử của Test :: Singleton là giáo dục. Gọi new()
luôn cho bạn cùng một đối tượng. Đầu tiên, tất cả dữ liệu được lưu trữ dưới dạng các biến lớp không có gì trong chính đối tượng. Điều này hoạt động cho đến khi tôi muốn kiểm tra Test :: Builder với chính nó. Sau đó, tôi cần hai đối tượng Test :: Builder, một thiết lập như một hình nộm, để nắm bắt và kiểm tra hành vi và đầu ra của nó, và một đối tượng là đối tượng thử nghiệm thực sự. Tại thời điểm đó, Test :: Builder được tái cấu trúc thành một đối tượng thực sự. Đối tượng singleton được lưu trữ dưới dạng dữ liệu lớp và new()
sẽ luôn trả về nó. create()
đã được thêm vào để tạo một đối tượng mới và cho phép thử nghiệm.
Hiện tại, người dùng đang muốn thay đổi một số hành vi của Test :: Builder trong mô-đun của riêng họ, nhưng để những người khác ở một mình, trong khi lịch sử thử nghiệm vẫn phổ biến trên tất cả các mô-đun thử nghiệm. Điều đang xảy ra bây giờ là đối tượng Test :: Monolithic nguyên khối đang được chia thành các phần nhỏ hơn (lịch sử, đầu ra, định dạng ...) với một đối tượng Test :: Builder thu thập chúng lại với nhau. Bây giờ Test :: Builder không còn phải là một singleton. Các thành phần của nó, giống như lịch sử, có thể. Điều này đẩy sự cần thiết không thể thay đổi của một người độc thân xuống một cấp độ. Nó mang lại sự linh hoạt hơn cho người dùng đối với các mảnh ghép và kết hợp. Các đối tượng singleton nhỏ hơn bây giờ có thể chỉ lưu trữ dữ liệu, với các đối tượng chứa chúng quyết định cách sử dụng nó. Nó thậm chí còn cho phép một lớp Non-Test :: Builder chơi cùng bằng cách sử dụng lịch sử Test :: Builder và singletons đầu ra.
Dường như có sự thúc đẩy giữa việc phối hợp dữ liệu và tính linh hoạt của hành vi có thể được giảm thiểu bằng cách đặt đơn lẻ xung quanh dữ liệu được chia sẻ với lượng hành vi nhỏ nhất có thể để đảm bảo tính toàn vẹn của dữ liệu.
Khi bạn tải một đối tượng Thuộc tính cấu hình, từ cơ sở dữ liệu hoặc tệp, sẽ giúp nó có dạng đơn lẻ; không có lý do để tiếp tục đọc lại dữ liệu tĩnh sẽ không thay đổi trong khi máy chủ đang chạy.
Bạn có thể sử dụng Singleton khi triển khai mẫu Trạng thái (theo cách hiển thị trong sách GoF). Điều này là do các lớp Nhà nước cụ thể không có trạng thái của riêng chúng và thực hiện các hành động của chúng theo các lớp ngữ cảnh.
Bạn cũng có thể làm cho Tóm tắt Factory là một singleton.
setState()
trách nhiệm của bạn trong việc quyết định chính sách tạo nhà nước. Nó giúp nếu ngôn ngữ lập trình của bạn hỗ trợ các mẫu hoặc generic. Thay vì Singleton, bạn có thể sử dụng mẫu Monostate , trong đó việc khởi tạo một đối tượng trạng thái kết thúc việc sử dụng lại cùng một đối tượng trạng thái toàn cục / tĩnh. Cú pháp thay đổi trạng thái có thể không thay đổi, vì người dùng của bạn không cần biết rằng trạng thái khởi tạo là Monostate.
Tài nguyên dùng chung. Đặc biệt là trong PHP, một lớp cơ sở dữ liệu, một lớp mẫu và một lớp kho tổng hợp biến toàn cục. Tất cả phải được chia sẻ bởi tất cả các mô-đun / lớp đang được sử dụng trong suốt mã.
Đó là cách sử dụng đối tượng thực sự -> lớp mẫu chứa mẫu trang đang được xây dựng và nó được định hình, thêm, thay đổi bởi các mô-đun đang thêm vào đầu ra trang. Nó phải được giữ như một trường hợp duy nhất để điều này có thể xảy ra, và cơ sở dữ liệu cũng vậy. Với một cơ sở dữ liệu được chia sẻ, tất cả các lớp của mô-đun có thể có quyền truy cập vào các truy vấn và nhận được chúng mà không cần phải chạy lại chúng.
Một biến đơn depot biến toàn cầu cung cấp cho bạn một kho biến toàn cầu, đáng tin cậy và dễ sử dụng. Nó dọn dẹp mã của bạn rất nhiều. Hãy tưởng tượng có tất cả các giá trị cấu hình trong một mảng trong một singleton như:
$gb->config['hostname']
hoặc có tất cả các giá trị ngôn ngữ trong một mảng như:
$gb->lang['ENTER_USER']
Khi kết thúc việc chạy mã cho trang, bạn sẽ nhận được, bây giờ là một sự trưởng thành:
$template
Singleton, một $gb
singleton có mảng lang để thay thế vào nó, và tất cả đầu ra được tải và sẵn sàng. Bạn chỉ cần thay thế chúng thành các khóa hiện có trong giá trị trang của đối tượng mẫu trưởng thành và sau đó cung cấp nó cho người dùng.
Ưu điểm lớn của việc này là bạn có thể thực hiện bất kỳ xử lý hậu kỳ nào bạn thích trên bất cứ thứ gì. Bạn có thể chuyển tất cả các giá trị ngôn ngữ sang google dịch hoặc dịch vụ dịch thuật khác và lấy lại chúng, và thay thế chúng vào vị trí của chúng, ví dụ như đã dịch. hoặc, bạn có thể thay thế trong cấu trúc trang hoặc chuỗi nội dung theo ý muốn.
Nó có thể rất thực tế để cấu hình các mối quan tâm cơ sở hạ tầng cụ thể như các singletons hoặc các biến toàn cục. Ví dụ yêu thích của tôi về điều này là các khung Dependency Injection sử dụng các singletons để hoạt động như một điểm kết nối với khung.
Trong trường hợp này, bạn đang sử dụng một phụ thuộc vào cơ sở hạ tầng để đơn giản hóa việc sử dụng thư viện và tránh sự phức tạp không cần thiết.
Tôi sử dụng nó cho một đối tượng đóng gói các tham số dòng lệnh khi xử lý các mô đun có thể cắm được. Chương trình chính không biết các tham số dòng lệnh là gì đối với các mô-đun được tải (và thậm chí không biết mô-đun nào đang được tải). ví dụ: tải chính A, không cần bất kỳ tham số nào (vậy tại sao cần thêm một con trỏ / tham chiếu / bất cứ điều gì, tôi không chắc chắn - trông giống như ô nhiễm), sau đó tải các mô-đun X, Y và Z. Hai trong số này, giả sử X và Z, cần (hoặc chấp nhận) các tham số, vì vậy họ gọi lại cho singleton dòng lệnh để cho nó biết tham số nào sẽ chấp nhận và trong thời gian chạy họ gọi lại để tìm hiểu xem người dùng có thực sự đã chỉ định không của họ.
Theo nhiều cách, một singleton để xử lý các tham số CGI sẽ hoạt động tương tự nếu bạn chỉ sử dụng một quy trình cho mỗi truy vấn (các phương thức mod_ * khác không làm điều này, vì vậy nó sẽ rất tệ ở đó - do đó, đối số nói rằng bạn không nên ' t sử dụng singletons trong thế giới mod_cgi trong trường hợp bạn chuyển sang mod_perl hoặc bất cứ thế giới nào).
Một ví dụ với mã, có lẽ.
Ở đây, ConcreteRegistry là một đơn vị trong trò chơi poker cho phép các hành vi trên toàn bộ cây gói truy cập vào một vài giao diện cốt lõi của trò chơi (ví dụ: mặt tiền cho mô hình, chế độ xem, bộ điều khiển, môi trường, v.v.):
http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html
Ed.
1 - Một nhận xét về câu trả lời đầu tiên:
Tôi không đồng ý với lớp Logger tĩnh. điều này có thể thực tế cho việc thực hiện, nhưng nó không thể thay thế cho thử nghiệm đơn vị. Một lớp tĩnh không thể được thay thế bằng một bài kiểm tra kép. Nếu bạn không kiểm tra đơn vị, bạn sẽ không thấy vấn đề ở đây.
2 - Tôi cố gắng không tạo ra một singleton bằng tay. Tôi chỉ tạo một đối tượng đơn giản với các hàm tạo cho phép tôi đưa cộng tác viên vào đối tượng. Nếu tôi cần một singleton, tôi sẽ sử dụng khung inyection phụ thuộc (Spring.NET, Unity cho .NET, Spring cho Java) hoặc một số thứ khác.
ILogger logger = Logger.SingleInstance();
trong đó phương thức này là tĩnh và nó trả về một thể hiện được lưu trữ tĩnh của ILogger. Bạn đã sử dụng ví dụ về "khung tiêm phụ thuộc". Gần như tất cả các container DI đều là singletons; cấu hình của chúng được xác định tĩnh và cuối cùng được truy cập từ / được lưu trữ trong một giao diện nhà cung cấp dịch vụ duy nhất.