Vậy Singletons là xấu, thì sao?


553

Gần đây đã có rất nhiều cuộc thảo luận về các vấn đề với việc sử dụng (và lạm dụng) Singletons. Tôi cũng từng là một trong những người đó trong sự nghiệp của mình. Tôi có thể thấy vấn đề hiện tại là gì, tuy nhiên, vẫn còn nhiều trường hợp tôi không thể thấy một sự thay thế tốt đẹp - và không có nhiều cuộc thảo luận chống Singleton thực sự cung cấp một vấn đề.

Đây là một ví dụ thực tế từ một dự án lớn gần đây tôi đã tham gia:

Ứng dụng này là một máy khách dày với nhiều màn hình và thành phần riêng biệt sử dụng lượng dữ liệu khổng lồ từ trạng thái máy chủ không được cập nhật quá thường xuyên. Dữ liệu này về cơ bản được lưu trữ trong một đối tượng "người quản lý" Singleton - "trạng thái toàn cầu" đáng sợ. Ý tưởng là có một vị trí này trong ứng dụng giữ dữ liệu được lưu trữ và đồng bộ hóa, và sau đó bất kỳ màn hình mới nào được mở có thể chỉ cần truy vấn hầu hết những gì họ cần từ đó, mà không yêu cầu lặp lại các dữ liệu hỗ trợ khác nhau từ máy chủ. Liên tục yêu cầu máy chủ sẽ mất quá nhiều băng thông - và tôi đang nói thêm hàng ngàn đô la hóa đơn Internet mỗi tuần, vì vậy điều đó không thể chấp nhận được.

Có cách tiếp cận nào khác có thể phù hợp ở đây hơn là về cơ bản có loại đối tượng bộ đệm bộ quản lý dữ liệu toàn cầu này không? Tất nhiên, đối tượng này không phải là một "Singleton", nhưng về mặt khái niệm nó có ý nghĩa là một. Một thay thế sạch đẹp ở đây là gì?


10
Vấn đề gì là việc sử dụng Singleton để giải quyết? Làm thế nào tốt hơn trong việc giải quyết vấn đề đó so với các lựa chọn thay thế (chẳng hạn như một lớp tĩnh)?
Anon.

14
@Anon: Làm thế nào để sử dụng một lớp tĩnh làm cho tình hình tốt hơn. Vẫn còn khớp nối chặt chẽ?
Martin York

5
@Martin: Tôi không gợi ý rằng nó làm cho nó "tốt hơn". Tôi đề nghị rằng trong hầu hết các trường hợp, singleton là một giải pháp để tìm kiếm một vấn đề.
Anon.

9
@Anon: Không đúng. Các lớp tĩnh cung cấp cho bạn (hầu như) không có quyền kiểm soát khởi tạo và làm cho đa luồng thậm chí còn khó khăn hơn so với Singletons (vì bạn phải tuần tự hóa truy cập vào mọi phương thức riêng lẻ thay vì chỉ cá thể). Singletons ít nhất cũng có thể thực hiện một giao diện, mà các lớp tĩnh không thể. Các lớp tĩnh chắc chắn có lợi thế của chúng, nhưng trong trường hợp này, Singleton chắc chắn là kẻ kém hơn trong hai tệ nạn đáng kể. Một lớp tĩnh thực hiện bất kỳ trạng thái có thể thay đổi nào giống như một đèn neon nhấp nháy lớn "CẢNH BÁO: THIẾT KẾ BAD AADAD!" ký tên.
Aaronaught

7
@Aaronaught: Nếu bạn chỉ đồng bộ hóa quyền truy cập vào singleton, thì đồng thời của bạn bị hỏng . Chủ đề của bạn có thể bị gián đoạn ngay sau khi tìm nạp đối tượng singleton, một chủ đề khác xuất hiện và điều kiện đua xe. Sử dụng Singleton thay vì một lớp tĩnh, trong hầu hết các trường hợp, chỉ cần lấy các dấu hiệu cảnh báo đi và nghĩ rằng giải quyết vấn đề .
Anon.

Câu trả lời:


809

Điều quan trọng là phải phân biệt ở đây giữa các trường hợp đơnmẫu thiết kế Singleton .

Các trường hợp đơn giản chỉ là một thực tế. Hầu hết các ứng dụng chỉ được thiết kế để hoạt động với một cấu hình tại một thời điểm, một giao diện người dùng tại một thời điểm, một hệ thống tệp tại một thời điểm, v.v. Nếu có rất nhiều trạng thái hoặc dữ liệu được duy trì, thì chắc chắn bạn sẽ muốn chỉ có một phiên bản và giữ cho nó tồn tại càng lâu càng tốt.

Mẫu thiết kế Singleton là một loại ví dụ rất cụ thể , cụ thể là:

  • Có thể truy cập thông qua một trường toàn cầu, tĩnh;
  • Được tạo hoặc khi khởi tạo chương trình hoặc khi truy cập lần đầu;
  • Không có nhà xây dựng công cộng (không thể khởi tạo trực tiếp);
  • Không bao giờ được giải phóng rõ ràng (hoàn toàn giải phóng khi chấm dứt chương trình).

Chính vì sự lựa chọn thiết kế cụ thể này mà mẫu này đưa ra một số vấn đề dài hạn tiềm ẩn:

  • Không có khả năng sử dụng các lớp trừu tượng hoặc giao diện;
  • Không có khả năng phân lớp;
  • Khớp nối cao trên ứng dụng (khó sửa đổi);
  • Khó kiểm tra (không thể giả / giả trong các bài kiểm tra đơn vị);
  • Khó song song trong trường hợp trạng thái đột biến (yêu cầu khóa mở rộng);
  • vân vân

Không có triệu chứng nào trong số này thực sự là đặc hữu của các trường hợp đơn lẻ, chỉ là mẫu Singleton.

Bạn có thể làm gì thay thế? Đơn giản là đừng sử dụng mẫu Singleton.

Trích dẫn từ câu hỏi:

Ý tưởng là có một vị trí này trong ứng dụng giữ dữ liệu được lưu trữ và đồng bộ hóa, và sau đó bất kỳ màn hình mới nào được mở có thể chỉ cần truy vấn hầu hết những gì họ cần từ đó, mà không yêu cầu lặp lại các dữ liệu hỗ trợ khác nhau từ máy chủ. Liên tục yêu cầu máy chủ sẽ mất quá nhiều băng thông - và tôi đang nói thêm hàng ngàn đô la hóa đơn Internet mỗi tuần, vì vậy điều đó không thể chấp nhận được.

Khái niệm này có một cái tên, như bạn sắp xếp gợi ý nhưng âm thanh không chắc chắn. Nó được gọi là bộ đệm . Nếu bạn muốn nhận được sự ưa thích, bạn có thể gọi nó là "bộ đệm ngoại tuyến" hoặc chỉ là một bản sao ngoại tuyến của dữ liệu từ xa.

Một bộ đệm không cần phải là một singleton. Nó có thể cần phải là một phiên bản duy nhất nếu bạn muốn tránh tìm nạp cùng một dữ liệu cho nhiều phiên bản bộ đệm; nhưng điều đó không có nghĩa là bạn thực sự phải phơi bày mọi thứ với mọi người .

Điều đầu tiên tôi làm là tách các khu vực chức năng khác nhau của bộ đệm thành các giao diện riêng biệt. Ví dụ: giả sử bạn đang tạo bản sao YouTube tồi tệ nhất thế giới dựa trên Microsoft Access:

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostP phổ biếnPage

Ở đây bạn có một vài giao diện mô tả cụ thể các loại dữ liệu một lớp học đặc biệt có thể cần truy cập vào - phương tiện truyền thông, hồ sơ người dùng, và các trang tĩnh (như trang bìa). Tất cả điều đó được thực hiện bởi một mega-cache, nhưng bạn thiết kế các lớp riêng lẻ của mình để chấp nhận các giao diện thay vào đó, vì vậy chúng không quan tâm loại cá thể nào chúng có. Bạn khởi tạo thể hiện vật lý một lần, khi chương trình của bạn bắt đầu, và sau đó chỉ bắt đầu chuyển qua các thể hiện (chuyển sang một loại giao diện cụ thể) thông qua các hàm tạo và thuộc tính công cộng.

Nhân tiện, đây được gọi là Tiêm phụ thuộc ; bạn không cần sử dụng Spring hoặc bất kỳ bộ chứa IoC đặc biệt nào, miễn là thiết kế lớp chung của bạn chấp nhận sự phụ thuộc của nó từ người gọi thay vì khởi tạo chúng trên trạng thái toàn cầu hoặc tham chiếu toàn bộ .

Tại sao bạn nên sử dụng thiết kế dựa trên giao diện? Ba lý do:

  1. Nó làm cho mã dễ đọc hơn; bạn có thể hiểu rõ ràng từ các giao diện chính xác dữ liệu mà các lớp phụ thuộc phụ thuộc vào.

  2. Nếu và khi bạn nhận ra rằng Microsoft Access không phải là lựa chọn tốt nhất cho back-end dữ liệu, bạn có thể thay thế nó bằng thứ gì đó tốt hơn - giả sử SQL Server.

  3. Nếu và khi bạn nhận ra rằng SQL Server không phải là lựa chọn tốt nhất cho phương tiện truyền thông đặc biệt , bạn có thể chia tay thực hiện của bạn mà không ảnh hưởng bất kỳ một phần khác của hệ thống . Đó là nơi sức mạnh thực sự của sự trừu tượng đến.

Nếu bạn muốn tiến thêm một bước thì bạn có thể sử dụng bộ chứa IoC (khung DI) như Spring (Java) hoặc Unity (.NET). Hầu hết mọi khung DI sẽ thực hiện quản lý trọn đời của riêng nó và đặc biệt cho phép bạn xác định một dịch vụ cụ thể là một thể hiện duy nhất (thường gọi nó là "singleton", nhưng đó chỉ là sự quen thuộc). Về cơ bản các khung này giúp bạn tiết kiệm phần lớn công việc của khỉ khi chuyển thủ công xung quanh các trường hợp, nhưng chúng không thực sự cần thiết. Bạn không cần bất kỳ công cụ đặc biệt nào để thực hiện thiết kế này.

Để hoàn thiện, tôi nên chỉ ra rằng thiết kế ở trên thực sự không lý tưởng. Khi bạn đang xử lý một bộ đệm (như bạn), bạn thực sự nên có một lớp hoàn toàn riêng biệt . Nói cách khác, một thiết kế như thế này:

                                                        + - Kho lưu trữ IMedia
                                                        |
                          Bộ nhớ cache (Chung) --------------- + - IProfileRep repository
                                ▲ |
                                | + - Kho lưu trữ IPage
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostP phổ biếnPage

Lợi ích của việc này là bạn thậm chí không bao giờ cần phải phá vỡ Cachetrường hợp của mình nếu bạn quyết định tái cấu trúc; bạn có thể thay đổi cách Media được lưu trữ đơn giản bằng cách cung cấp cho nó một triển khai thay thế IMediaRepository. Nếu bạn nghĩ về việc làm thế nào điều này khớp với nhau, bạn sẽ thấy rằng nó vẫn chỉ tạo ra một thể hiện vật lý của bộ đệm, vì vậy bạn không bao giờ cần phải tìm nạp cùng một dữ liệu hai lần.

Không có gì trong số này để nói rằng mọi phần mềm duy nhất trên thế giới cần phải được kiến ​​trúc theo các tiêu chuẩn chính xác này về sự gắn kết cao và khớp nối lỏng lẻo; nó phụ thuộc vào quy mô và phạm vi của dự án, nhóm của bạn, ngân sách của bạn, thời hạn, v.v. Nhưng nếu bạn đang hỏi thiết kế tốt nhất là gì (để sử dụng thay cho một đơn), thì đây là nó.

PS Như những người khác đã tuyên bố, có lẽ không phải là ý tưởng tốt nhất cho các lớp phụ thuộc nhận thức được rằng họ đang sử dụng bộ đệm - đó là một chi tiết triển khai mà họ đơn giản không bao giờ nên quan tâm. Điều đó đang được nói, kiến ​​trúc tổng thể vẫn sẽ trông rất giống với những gì trong hình trên, bạn sẽ không đề cập đến các giao diện riêng lẻ như Caches . Thay vào đó, bạn đặt tên cho chúng là Dịch vụ hoặc một cái gì đó tương tự.


131
Bài đăng đầu tiên tôi từng đọc thực sự giải thích DI là một thay thế cho trạng thái toàn cầu. Cảm ơn thời gian và nỗ lực đưa vào này. Tất cả chúng ta tốt hơn là kết quả của bài viết này.
MrLane

4
Tại sao Cache không thể là một singleton? Nó không phải là một singleton nếu bạn vượt qua nó và sử dụng tiêm phụ thuộc? Singleton chỉ giới hạn bản thân trong một trường hợp chứ không phải về cách truy cập đúng không? Hãy xem tôi đảm nhận điều này: assoc.tumblr.com/post/51302471844/the-misunder Hiểu
Erik Engheim

29
@AdamSmith: Bạn đã thực sự đọc bất kỳ câu trả lời này? Câu hỏi của bạn được trả lời trong hai đoạn đầu tiên. Mẫu đơn! == Đơn vị.
Aaronaught

5
@Cawas và Adam Smith - Đọc các liên kết của bạn Tôi có cảm giác bạn không thực sự đọc câu trả lời này - mọi thứ đã ở đó.
Wilbert

19
@Cawas Tôi cảm thấy rằng phần cốt lõi của câu trả lời này là sự phân biệt giữa trường hợp đơn và đơn. Singleton là xấu, đơn lẻ là không. Dependency Injection là một cách tổng quát, tốt đẹp để sử dụng các trường hợp đơn lẻ mà không phải sử dụng singletons.
Wilbert

48

Trong trường hợp bạn đưa ra, có vẻ như việc sử dụng Singleton không phải là vấn đề, mà là triệu chứng của một vấn đề - một vấn đề kiến ​​trúc lớn hơn.

Tại sao các màn hình truy vấn đối tượng bộ đệm cho dữ liệu? Bộ nhớ đệm phải được minh bạch cho khách hàng. Cần có một sự trừu tượng thích hợp để cung cấp dữ liệu và việc thực hiện sự trừu tượng hóa đó có thể sử dụng bộ nhớ đệm.

Vấn đề có thể là sự phụ thuộc giữa các bộ phận của hệ thống không được thiết lập chính xác và điều này có thể mang tính hệ thống.

Tại sao các màn hình cần phải có kiến ​​thức về nơi họ lấy dữ liệu của họ? Tại sao các màn hình không được cung cấp với một đối tượng có thể đáp ứng yêu cầu dữ liệu của họ (đằng sau đó bộ nhớ cache bị ẩn)? Thông thường, trách nhiệm tạo màn hình không tập trung, và do đó không có quan điểm rõ ràng về việc tiêm phụ thuộc.

Một lần nữa, chúng tôi đang xem xét các vấn đề kiến ​​trúc và thiết kế quy mô lớn.

Ngoài ra, điều rất quan trọng là phải hiểu rằng thời gian tồn tại của một đối tượng có thể được tách hoàn toàn khỏi cách tìm thấy đối tượng để sử dụng.

Bộ đệm sẽ phải tồn tại trong suốt vòng đời của ứng dụng (có ích), vì vậy thời gian tồn tại của đối tượng là của Singleton.

Nhưng vấn đề với Singleton (ít nhất là việc triển khai chung Singleton như một lớp / thuộc tính tĩnh), là cách các lớp khác sử dụng nó đi về việc tìm kiếm nó.

Với việc triển khai Singleton tĩnh, quy ước chỉ đơn giản là sử dụng nó bất cứ khi nào cần thiết. Nhưng điều đó hoàn toàn che giấu sự phụ thuộc và kết hợp chặt chẽ giữa hai lớp.

Nếu chúng tôi cung cấp sự phụ thuộc cho lớp, thì sự phụ thuộc đó là rõ ràng và tất cả các lớp tiêu thụ cần phải có kiến ​​thức về hợp đồng có sẵn để sử dụng.


2
Có một lượng dữ liệu khổng lồ mà một số màn hình nhất định có thể cần, nhưng không nhất thiết phải có. Và bạn không biết cho đến khi hành động của người dùng được thực hiện xác định điều này - và có rất nhiều, rất nhiều kết hợp. Vì vậy, cách nó được thực hiện là có một số dữ liệu chung chung được lưu trữ và đồng bộ hóa trong máy khách (phần lớn thu được khi đăng nhập), và sau đó các yêu cầu tiếp theo sẽ tạo bộ đệm nhiều hơn, vì dữ liệu được yêu cầu rõ ràng có xu hướng được sử dụng lại trong cùng một phiên. Trọng tâm là cắt giảm yêu cầu đến máy chủ, do đó cần có bộ đệm phía máy khách. <cont>
Bàn Bobby

1
<cont> Nó cơ bản là minh bạch. Theo nghĩa là có một cuộc gọi lại từ máy chủ nếu dữ liệu cần thiết nhất định chưa được lưu trữ. Nhưng việc triển khai (về mặt logic và vật lý) của trình quản lý bộ đệm đó là một Singleton.
Bàn Bobby

6
Tôi với qstarin ở đây: Các đối tượng truy cập dữ liệu không nên biết (hoặc cần biết) rằng dữ liệu được lưu trữ (đó là một chi tiết triển khai). Người sử dụng dữ liệu chỉ yêu cầu dữ liệu (hoặc yêu cầu giao diện để truy xuất dữ liệu).
Martin York

1
Bộ nhớ đệm IS về cơ bản là một chi tiết thực hiện. Có một giao diện thông qua đó dữ liệu được truy vấn và các đối tượng nhận được nó không biết liệu nó có đến từ bộ đệm hay không. Nhưng bên dưới trình quản lý bộ đệm này là một Singleton.
Bàn Bobby

2
@BOB Bàn: sau đó tình huống của bạn không nghiêm trọng như vẻ ngoài của nó. Singleton đó (giả sử bạn có nghĩa là một lớp tĩnh, không chỉ là một đối tượng với một thể hiện tồn tại miễn là ứng dụng) vẫn còn có vấn đề. Nó che giấu sự thật rằng đối tượng cung cấp dữ liệu của bạn có sự phụ thuộc vào nhà cung cấp bộ đệm. Nó là tốt hơn nếu đó là rõ ràng và bên ngoài. Tách chúng Nó là cần thiết cho testability mà bạn có thể dễ dàng thay thế linh kiện, và một nhà cung cấp bộ nhớ cache là một thủ dụ của một thành phần đó (mức độ thường xuyên là nhà cung cấp bộ nhớ cache được hỗ trợ bởi ASP.Net).
quentin-starin

45

Tôi đã viết cả một chương về câu hỏi này. Chủ yếu là trong bối cảnh của các trò chơi, nhưng hầu hết nên áp dụng bên ngoài các trò chơi.

tl; dr:

Mẫu Gang of Four Singleton thực hiện hai điều: cung cấp cho bạn quyền truy cập thuận tiện vào một đối tượng từ bất cứ đâu và đảm bảo rằng chỉ có một phiên bản của nó có thể được tạo. 99% thời gian, tất cả những gì bạn quan tâm là nửa đầu của điều đó, và kéo theo nửa sau để có thêm giới hạn không cần thiết.

Không chỉ vậy, nhưng có những giải pháp tốt hơn để cung cấp truy cập thuận tiện. Làm cho một đối tượng toàn cầu là tùy chọn hạt nhân để giải quyết điều đó, và làm cho nó dễ dàng để phá hủy sự đóng gói của bạn. Tất cả mọi thứ xấu về toàn cầu đều áp dụng hoàn toàn cho người độc thân.

Nếu bạn đang sử dụng nó chỉ vì bạn có nhiều vị trí trong mã cần chạm vào cùng một đối tượng, hãy thử tìm một cách tốt hơn để cung cấp cho chỉ những đối tượng đó mà không để lộ ra toàn bộ cơ sở mã. Các giải pháp khác:

  • Bỏ nó hoàn toàn. Tôi đã thấy rất nhiều lớp học đơn lẻ không có bất kỳ trạng thái nào và chỉ là túi chức năng của người trợ giúp. Những người không cần một ví dụ nào cả. Chỉ cần biến chúng thành các hàm tĩnh hoặc di chuyển chúng vào một trong các lớp mà hàm lấy làm đối số. Bạn sẽ không cần một Mathlớp học đặc biệt nếu bạn có thể làm 123.Abs().

  • Vượt qua. Giải pháp đơn giản nếu một phương thức cần một số đối tượng khác là chỉ truyền nó vào. Không có gì sai khi truyền một số đối tượng xung quanh.

  • Đặt nó trong lớp cơ sở. Nếu bạn có rất nhiều lớp mà tất cả đều cần truy cập vào một số đối tượng đặc biệt và chúng chia sẻ một lớp cơ sở, bạn có thể biến đối tượng đó thành thành viên trên cơ sở. Khi bạn xây dựng nó, vượt qua trong đối tượng. Bây giờ tất cả các đối tượng dẫn xuất có thể có được nó khi họ cần. Nếu bạn làm cho nó được bảo vệ, bạn đảm bảo đối tượng vẫn được đóng gói.


1
Bạn có thể tóm tắt ở đây?
Nicole

1
Xong, nhưng tôi vẫn khuyến khích bạn đọc cả chương.
khoan hồng

4
Một cách khác: Tiêm phụ thuộc!
Brad Cupit

1
@BradCupit anh ấy cũng nói về điều đó trên đường link ... Tôi phải nói rằng tôi vẫn đang cố gắng tiêu hóa tất cả. Nhưng nó đã được đọc một cách rõ ràng nhất trên các singletons tôi từng đọc. Cho đến nay, tôi là những người độc thân tích cực, cũng giống như các vars toàn cầu, và tôi đang quảng bá cho Toolbox . Bây giờ tôi không biết nữa. Ông munificient có thể cho tôi biết nếu bộ định vị dịch vụ chỉ là một hộp công cụ tĩnh ? Sẽ không tốt hơn nếu biến nó thành một singleton (do đó, một hộp công cụ)?
cregox

1
"Hộp công cụ" trông khá giống với "Trình định vị dịch vụ" đối với tôi. Cho dù bạn sử dụng tĩnh cho nó hay tự biến nó thành một singleton, tôi nghĩ, điều đó không quan trọng đối với hầu hết các chương trình. Tôi có xu hướng nghiêng về thống kê bởi vì tại sao phải đối phó với việc khởi tạo lười biếng và phân bổ đống nếu bạn không phải làm vậy?
khoan hồng

21

Nó không phải là nhà nước toàn cầu, đó là vấn đề.

Thực sự bạn chỉ cần lo lắng về global mutable state. Trạng thái không đổi không bị ảnh hưởng bởi các tác động phụ và do đó ít xảy ra vấn đề hơn.

Mối quan tâm chính với singleton là nó thêm khớp nối và do đó làm cho mọi thứ như kiểm tra khó khăn (er). Bạn có thể giảm khớp nối bằng cách lấy singleton từ một nguồn khác (ví dụ: nhà máy). Điều này sẽ cho phép bạn tách mã từ một trường hợp cụ thể (mặc dù bạn trở nên gắn kết hơn với nhà máy (nhưng ít nhất nhà máy có thể có các triển khai thay thế cho các giai đoạn khác nhau)).

Trong tình huống của bạn, tôi nghĩ rằng bạn có thể thoát khỏi nó miễn là singleton của bạn thực sự thực hiện một giao diện (để có thể sử dụng một giải pháp thay thế trong các tình huống khác).

Nhưng một nhược điểm lớn khác với singletons là một khi chúng được đặt tại chỗ để loại bỏ chúng khỏi mã và thay thế chúng bằng một thứ khác sẽ trở thành một nhiệm vụ khó khăn thực sự (lại có khớp nối đó).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

Điều đó có ý nghĩa. Điều này cũng khiến tôi nghĩ rằng tôi chưa bao giờ thực sự lạm dụng Singletons, mà chỉ bắt đầu nghi ngờ BẤT K use việc sử dụng chúng. Nhưng không thể nghĩ ra bất kỳ sự lạm dụng hoàn toàn nào mà tôi đã làm, theo những điểm này. :)
Bàn Bobby

2
(bạn có thể có nghĩa là cho mỗi gia nhập không phải là "mỗi nói")
nohat

4
@nohat: Tôi là người bản ngữ của "Tiếng Anh nữ hoàng" và do đó từ chối mọi thứ tiếng Pháp tìm kiếm trừ khi chúng tôi làm cho nó tốt hơn (như le weekendoops đó là một trong số chúng tôi). Cảm ơn :-)
Martin York

21
mỗi se là tiếng Latin.
Anon.

2
@Anon: OK. Điều đó không tệ lắm sau đó ;-)
Martin York

19

Rồi sao? Vì không ai nói điều đó: Hộp công cụ . Đó là nếu bạn muốn các biến toàn cầu .

Lạm dụng đơn lẻ có thể tránh được bằng cách nhìn vấn đề từ một góc độ khác. Giả sử một ứng dụng chỉ cần một thể hiện của một lớp và ứng dụng sẽ cấu hình lớp đó khi khởi động: Tại sao chính lớp đó phải chịu trách nhiệm cho việc trở thành một người độc thân? Có vẻ khá logic cho ứng dụng đảm nhận trách nhiệm này, vì ứng dụng yêu cầu loại hành vi này. Ứng dụng, không phải là thành phần, nên là đơn. Sau đó, ứng dụng tạo một phiên bản của thành phần có sẵn cho bất kỳ mã dành riêng cho ứng dụng nào để sử dụng. Khi một ứng dụng sử dụng một số thành phần như vậy, nó có thể tổng hợp chúng thành cái mà chúng ta gọi là hộp công cụ.

Nói một cách đơn giản, hộp công cụ của ứng dụng là một singleton chịu trách nhiệm tự cấu hình hoặc cho phép cơ chế khởi động của ứng dụng định cấu hình nó ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Nhưng đoán xem? Nó là một người độc thân!

Và một singleton là gì?

Có lẽ đó là nơi sự nhầm lẫn bắt đầu.

Đối với tôi, singleton là một đối tượng được thi hành để chỉ có một thể hiện duy nhất và luôn luôn. Bạn có thể truy cập nó ở bất cứ đâu, bất cứ lúc nào mà không cần phải khởi tạo nó. Đó là lý do tại sao nó liên quan chặt chẽ đến static. Để so sánh, staticvề cơ bản là điều tương tự, ngoại trừ nó không phải là một ví dụ. Chúng tôi không cần phải khởi tạo nó, thậm chí chúng tôi không thể, vì nó được phân bổ tự động. Và điều đó có thể và không mang lại vấn đề.

Từ kinh nghiệm của tôi, chỉ cần thay thế staticcho Singleton đã giải quyết được nhiều vấn đề trong một dự án túi chắp vá cỡ trung bình mà tôi đang thực hiện. Điều đó chỉ có nghĩa là nó có một số cách sử dụng cho các dự án được thiết kế xấu. Tôi nghĩ rằng có quá nhiều cuộc thảo luận nếu mẫu singletonhữu ích hay không và tôi thực sự không thể tranh luận nếu nó thực sự xấu . Nhưng vẫn có những lý lẽ tốt có lợi cho singleton so với các phương thức tĩnh, nói chung .

Điều duy nhất tôi chắc chắn là không tốt về singletons, là khi chúng ta sử dụng chúng trong khi bỏ qua các thực hành tốt. Đó thực sự là một cái gì đó không dễ dàng để đối phó. Nhưng thực hành xấu có thể được áp dụng cho bất kỳ mô hình. Và, tôi biết, thật quá chung chung để nói rằng ... Ý tôi là có quá nhiều thứ cho nó.

Đừng hiểu lầm tôi!

Nói một cách đơn giản, giống như vars toàn cầu , singletons vẫn nên tránh mọi lúc . Đặc biệt bởi vì họ bị lạm dụng quá mức. Nhưng vars toàn cầu không thể luôn luôn tránh và chúng ta nên sử dụng chúng trong trường hợp cuối cùng đó.

Dù sao, có nhiều đề xuất khác ngoài Hộp công cụ, và giống như hộp công cụ, mỗi người đều có ứng dụng của mình ...

Các lựa chọn thay thế khác

  • Các bài viết tốt nhất mà tôi vừa đọc về độc thân gợi ý Service Locator như một sự thay thế. Đối với tôi về cơ bản đó là một " Hộp công cụ tĩnh ", nếu bạn muốn. Nói cách khác, biến Bộ định vị dịch vụ thành Singleton và bạn có Hộp công cụ. Tất nhiên, điều đó đi ngược lại với đề xuất ban đầu về việc tránh singleton, nhưng điều đó chỉ để thực thi vấn đề của singleton là cách nó được sử dụng chứ không phải bản thân mô hình.

  • Những người khác đề nghị Mô hình nhà máy như là một thay thế. Đó là sự thay thế đầu tiên tôi nghe được từ một đồng nghiệp và chúng tôi đã nhanh chóng loại bỏ nó để sử dụng làm var toàn cầu . Nó chắc chắn có cách sử dụng của nó, nhưng singletons cũng vậy.

Cả hai lựa chọn thay thế ở trên là những lựa chọn thay thế tốt. Nhưng tất cả phụ thuộc vào cách sử dụng của bạn.

Bây giờ, việc tránh những người độc thân nên tránh bằng mọi giá chỉ là sai ...

  • Câu trả lời của Aaronaught đề nghị không bao giờ sử dụng singletons , vì một loạt lý do. Nhưng tất cả đều là lý do chống lại việc nó bị sử dụng và lạm dụng như thế nào, không trực tiếp chống lại mô hình đó. Tôi đồng ý với tất cả những lo lắng về những điểm đó, làm thế nào tôi có thể? Tôi chỉ nghĩ rằng nó sai lệch.

Không có khả năng (để trừu tượng hoặc lớp con) thực sự là có, nhưng vì vậy những gì? Nó không có nghĩa cho điều đó. Không có khả năng giao diện , như tôi có thể nói . Khớp nối cao cũng có thể ở đó, nhưng đó chỉ là vì cách nó thường được sử dụng. Nó không phải . Trong thực tế, bản thân khớp nối không liên quan gì đến mẫu singleton. Điều đó đã được làm rõ, nó cũng đã loại bỏ những khó khăn để kiểm tra. Đối với khó khăn để song song, điều đó phụ thuộc vào ngôn ngữ và nền tảng, do đó, một lần nữa, không phải là một vấn đề trên mẫu.

Ví dụ thực tế

Tôi thường thấy 2 được sử dụng, cả ủng hộ và chống lại những người độc thân. Web cache (trường hợp của tôi) và dịch vụ đăng nhập .

Việc ghi nhật ký, một số người sẽ tranh luận , là một ví dụ đơn lẻ hoàn hảo, bởi vì, và tôi trích dẫn:

  • Người yêu cầu cần một đối tượng nổi tiếng để gửi yêu cầu đăng nhập. Điều này có nghĩa là một điểm truy cập toàn cầu.
  • Vì dịch vụ ghi nhật ký là một nguồn sự kiện duy nhất mà nhiều người nghe có thể đăng ký, nên chỉ cần có một phiên bản.
  • Mặc dù các ứng dụng khác nhau có thể đăng nhập vào các thiết bị đầu ra khác nhau, cách chúng đăng ký trình nghe của chúng luôn giống nhau. Tất cả các tùy chỉnh được thực hiện thông qua người nghe. Khách hàng có thể yêu cầu đăng nhập mà không biết văn bản sẽ được ghi lại như thế nào hoặc ở đâu. Do đó, mọi ứng dụng sẽ sử dụng dịch vụ ghi nhật ký theo cùng một cách.
  • Bất kỳ ứng dụng nào cũng có thể thoát khỏi chỉ với một phiên bản của dịch vụ ghi nhật ký.
  • Bất kỳ đối tượng nào cũng có thể là người yêu cầu đăng nhập, bao gồm các thành phần có thể tái sử dụng, do đó chúng không nên được ghép nối với bất kỳ ứng dụng cụ thể nào.

Mặc dù những người khác sẽ lập luận rằng việc mở rộng dịch vụ nhật ký trở nên khó khăn khi bạn cuối cùng nhận ra rằng nó thực sự không nên chỉ là một trường hợp.

Vâng, tôi nói cả hai đối số là hợp lệ. Vấn đề ở đây, một lần nữa, không nằm ở mẫu đơn. Đó là quyết định kiến ​​trúc và trọng số nếu tái cấu trúc là một rủi ro khả thi. Đó là một vấn đề nữa khi, thông thường, tái cấu trúc là biện pháp khắc phục cần thiết cuối cùng.


@gnat Cảm ơn! Tôi chỉ nghĩ đến việc chỉnh sửa câu trả lời để thêm một số cảnh báo về việc sử dụng Singletons ... Câu nói của bạn hoàn toàn phù hợp!
cregox

2
rất vui vì bạn thích nó Không chắc chắn liệu điều này có giúp tránh được việc hạ thấp hay không - có lẽ độc giả đang gặp khó khăn trong việc kết nối điểm được đưa ra trong bài đăng này với vấn đề cụ thể được đặt ra trong câu hỏi, đặc biệt là trong ánh sáng của phân tích tuyệt vời được cung cấp trong câu trả lời trước
gnat

@gnat yeah, tôi biết đây là một trận chiến dài. Tôi hy vọng thời gian sẽ trả lời. ;-)
cregox

1
Tôi đồng ý với định hướng chung của bạn ở đây, mặc dù có lẽ hơi quá nhiệt tình về một thư viện dường như không khác gì một kho chứa IoC cực kỳ đơn giản (ví dụ, thậm chí còn cơ bản hơn cả Funq ). Dịch vụ định vị thực sự là một mô hình chống; đó là một công cụ hữu ích chủ yếu trong các dự án cũ / nâu, cùng với "người nghèo DI", nơi sẽ quá tốn kém để tái cấu trúc mọi thứ để sử dụng một bộ chứa IoC đúng cách.
Aaronaught

1
@Cawas: Ah, xin lỗi - đã nhầm lẫn với java.awt.Toolkit. Quan điểm của tôi cũng giống như vậy: Toolboxnghe giống như một túi đựng các mảnh và mảnh không liên quan, thay vì một lớp kết hợp với một mục đích duy nhất. Nghe có vẻ không phải là thiết kế tốt với tôi. (Lưu ý rằng bài viết bạn đề cập đến là từ năm 2001, trước khi tiêm phụ thuộc và các thùng chứa DI đã trở nên phổ biến.)
Jon Skeet

5

Vấn đề chính của tôi với mẫu thiết kế singleton là rất khó để viết các bài kiểm tra đơn vị tốt cho ứng dụng của bạn.

Mọi thành phần có sự phụ thuộc vào "trình quản lý" này đều thực hiện bằng cách truy vấn thể hiện đơn lẻ của nó. Và nếu bạn muốn viết một bài kiểm tra đơn vị cho một thành phần như vậy, bạn phải đưa dữ liệu vào trường hợp đơn lẻ này, điều này có thể không dễ dàng.

Mặt khác, nếu "trình quản lý" của bạn được đưa vào các thành phần phụ thuộc thông qua tham số hàm tạo và thành phần đó không biết loại cụ thể của trình quản lý, chỉ có một giao diện hoặc lớp cơ sở trừu tượng mà trình quản lý thực hiện, sau đó là một đơn vị kiểm tra có thể cung cấp các triển khai thay thế của người quản lý khi kiểm tra các phụ thuộc.

Nếu bạn sử dụng các bộ chứa IOC để định cấu hình và khởi tạo các thành phần tạo nên ứng dụng của mình, thì bạn có thể dễ dàng định cấu hình bộ chứa IOC của mình để chỉ tạo một phiên bản của "trình quản lý", cho phép bạn đạt được cùng một, chỉ một trường hợp kiểm soát bộ đệm ứng dụng toàn cầu .

Nhưng nếu bạn không quan tâm đến các bài kiểm tra đơn vị, thì một mẫu thiết kế đơn có thể hoàn toàn ổn. (nhưng dù sao tôi cũng sẽ không làm điều đó)


Giải thích rõ ràng, đây là câu trả lời giải thích rõ nhất về vấn đề kiểm tra Singletons
José Tomás Tocino

4

Một singleton không phải là một cách cơ bản xấu , theo nghĩa là bất cứ điều gì thiết kế điện toán có thể là tốt hoặc xấu. Nó chỉ có thể đúng (cho kết quả mong đợi) hoặc không. Nó cũng có thể hữu ích hoặc không, nếu nó làm cho mã rõ ràng hơn hoặc hiệu quả hơn.

Một trường hợp trong đó singletons hữu ích là khi chúng đại diện cho một thực thể thực sự là duy nhất. Trong hầu hết các môi trường, cơ sở dữ liệu là duy nhất, thực sự chỉ có một cơ sở dữ liệu. Kết nối với cơ sở dữ liệu đó có thể phức tạp vì nó yêu cầu quyền đặc biệt hoặc duyệt qua một số loại kết nối. Tổ chức kết nối đó vào một singleton có lẽ rất có ý nghĩa cho lý do này một mình.

Nhưng bạn cũng cần chắc chắn rằng singleton thực sự là một singleton chứ không phải là biến toàn cục. Điều này quan trọng khi cơ sở dữ liệu duy nhất, duy nhất thực sự là 4 cơ sở dữ liệu, mỗi cơ sở cho các sản phẩm, dàn dựng, phát triển và thử nghiệm. Cơ sở dữ liệu Singleton sẽ tìm ra cái nào sẽ được kết nối với, lấy ví dụ duy nhất cho cơ sở dữ liệu đó, kết nối nó nếu cần và trả lại cho người gọi.

Khi một singleton không thực sự là một singleton (đây là lúc hầu hết các lập trình viên buồn bã), đó là một thế giới tức thời uể oải, không có cơ hội để đưa ra một ví dụ chính xác.

Một tính năng hữu ích khác của một mẫu singleton được thiết kế tốt là nó thường không thể quan sát được. Người gọi yêu cầu kết nối. Dịch vụ cung cấp nó có thể trả về một đối tượng được gộp hoặc nếu nó đang thực hiện kiểm tra, nó có thể tạo một đối tượng mới cho mỗi người gọi hoặc thay vào đó cung cấp một đối tượng giả.


3

Việc sử dụng mẫu singleton đại diện cho các đối tượng thực tế là hoàn toàn chấp nhận được. Tôi viết cho iPhone và có rất nhiều singletons trong khung Cacao Touch. Ứng dụng này được đại diện bởi một singleton của lớp UIApplication. Bạn chỉ có một ứng dụng, vì vậy nó phù hợp để đại diện cho ứng dụng đó.

Sử dụng một singleton như một lớp trình quản lý dữ liệu là được miễn là nó được thiết kế đúng. Nếu đó là một nhóm các thuộc tính dữ liệu, điều đó không tốt hơn phạm vi toàn cầu. Nếu đó là một tập hợp các getters và setters, điều đó tốt hơn, nhưng vẫn không tuyệt vời. Nếu đó là một lớp thực sự quản lý tất cả giao diện với dữ liệu, bao gồm cả việc tìm nạp dữ liệu từ xa, bộ nhớ đệm, thiết lập và phân tích ... Điều đó có thể rất hữu ích.


2

Singletons chỉ là hình chiếu của một kiến ​​trúc hướng dịch vụ vào một chương trình.

API là một ví dụ về singleton ở cấp độ giao thức. Bạn truy cập Twitter, Google, vv thông qua những gì cơ bản là singletons. Vậy tại sao singletons trở nên xấu trong một chương trình?

Nó phụ thuộc vào cách bạn nghĩ về một chương trình. Nếu bạn nghĩ về một chương trình như một xã hội của các dịch vụ thay vì các trường hợp lưu trữ bị ràng buộc ngẫu nhiên thì singletons có ý nghĩa hoàn hảo.

Singletons là một điểm truy cập dịch vụ. Giao diện công cộng cho một thư viện chức năng ràng buộc chặt chẽ có lẽ ẩn chứa một kiến ​​trúc nội bộ rất tinh vi.

Vì vậy, tôi không thấy một singleton khác với một nhà máy. Ví dụ, singleton có thể có các tham số hàm tạo. Nó có thể được tạo bởi một số ngữ cảnh biết cách giải quyết máy in mặc định đối với tất cả các cơ chế lựa chọn có thể, ví dụ. Để thử nghiệm, bạn có thể chèn bản giả của riêng bạn. Vì vậy, nó có thể khá linh hoạt.

Khóa này nằm trong một chương trình khi tôi thực thi và cần một chút chức năng, tôi có thể truy cập vào singleton với sự tự tin hoàn toàn rằng dịch vụ đã sẵn sàng và sẵn sàng để sử dụng. Đây là chìa khóa khi có các luồng khác nhau bắt đầu trong một quy trình phải đi qua một máy trạng thái để được coi là sẵn sàng.

Thông thường tôi sẽ bọc một XxxServicelớp bao bọc một người độc thân quanh lớp Xxx. Người độc thân hoàn toàn không ở trong lớp Xxx, nó tách ra thành một lớp khác XxxService. Điều này là do Xxxcó thể có nhiều phiên bản, mặc dù không có khả năng, nhưng chúng tôi vẫn muốn có một Xxxphiên bản có thể truy cập trên toàn cầu trên mỗi hệ thống. XxxServicecung cấp một sự tách biệt tốt đẹp của mối quan tâm. Xxxkhông phải thực thi chính sách đơn lẻ, nhưng chúng ta có thể sử dụng Xxxnhư một đơn vị khi chúng ta cần.

Cái gì đó như:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

Câu hỏi đầu tiên, bạn có tìm thấy rất nhiều lỗi trong ứng dụng không? có lẽ quên cập nhật bộ đệm, hoặc bộ đệm xấu hoặc khó thay đổi? (tôi nhớ một ứng dụng sẽ không thay đổi kích thước trừ khi bạn cũng thay đổi màu sắc ... tuy nhiên bạn có thể thay đổi màu trở lại và giữ nguyên kích thước).

Những gì bạn sẽ làm là có lớp đó nhưng XÓA TẤT CẢ CÁC THÀNH VIÊN TÌNH TRẠNG. Ok đây không phải là không cần thiết nhưng tôi khuyên bạn nên nó. Thực sự bạn chỉ cần khởi tạo lớp như một lớp bình thường và PASS con trỏ vào. Đừng frigen nói ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()

Nó làm việc nhiều hơn nhưng thực sự, nó ít gây nhầm lẫn. Một số nơi bạn không nên thay đổi những thứ mà bây giờ bạn không thể vì nó không còn toàn cầu. Tất cả các lớp quản lý của tôi là các lớp thông thường, chỉ cần coi nó như vậy.


1

IMO, ví dụ của bạn nghe ổn. Tôi đề nghị bao thanh toán như sau: đối tượng bộ đệm cho từng đối tượng dữ liệu (và đằng sau mỗi); các đối tượng bộ đệm và các đối tượng truy cập db có cùng giao diện. Điều này mang lại khả năng trao đổi bộ nhớ cache trong và ngoài mã; cộng với nó cho một lộ trình mở rộng dễ dàng.

Đồ họa:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

Bộ truy cập DB và bộ đệm có thể kế thừa từ cùng một đối tượng hoặc loại vịt thành trông giống như cùng một đối tượng, bất cứ điều gì. Miễn là bạn có thể cắm / biên dịch / kiểm tra và nó vẫn hoạt động.

Điều này tách riêng mọi thứ để bạn có thể thêm bộ nhớ cache mới mà không cần phải đi vào và sửa đổi một số đối tượng Uber-Cache. YMMV. IANAL. VÂN VÂN.


1

Một chút muộn để tiệc tùng, nhưng dù sao.

Singleton là một công cụ trong hộp công cụ, giống như mọi thứ khác. Hy vọng rằng bạn có nhiều hơn trong hộp công cụ của bạn hơn là chỉ một cái búa.

Xem xét điều này:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

đấu với

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

Trường hợp thứ nhất dẫn đến khớp nối cao vv; Cách thứ 2 không có vấn đề gì @Aaronaught đang mô tả, theo như tôi có thể nói. Đó là tất cả về cách bạn sử dụng nó.


Không đồng ý. Mặc dù cách thứ hai là "tốt hơn" (giảm khớp nối), nó vẫn có những vấn đề được DI giải quyết. Bạn không nên dựa vào người tiêu dùng của lớp để cung cấp việc triển khai các dịch vụ của mình - điều đó được thực hiện tốt hơn trong hàm tạo khi lớp được tạo. Giao diện của bạn chỉ nên yêu cầu tối thiểu về mặt đối số. Ngoài ra còn có một cơ hội tốt rằng lớp của bạn yêu cầu một trường hợp duy nhất để hoạt động - một lần nữa, việc dựa vào người tiêu dùng để thực thi quy tắc này là rủi ro và không cần thiết.
AlexFoxGill

Đôi khi Di là một quá mức cho một nhiệm vụ nhất định. Phương thức trong mã mẫu có thể là một hàm tạo, nhưng không nhất thiết - mà không cần nhìn vào ví dụ cụ thể, đó là một đối số tranh luận. Ngoài ra, DoS Something có thể lấy IS Something và MySingleton có thể triển khai giao diện đó - ok, đó không phải trong một mẫu .. mà chỉ là một mẫu.
Evgeni

1

Có mỗi màn hình đưa Trình quản lý vào hàm tạo của chúng.

Khi bạn khởi động ứng dụng của mình, bạn tạo một phiên bản của trình quản lý và chuyển nó đi xung quanh.

Điều này được gọi là Inversion of Control và cho phép bạn trao đổi bộ điều khiển khi cấu hình thay đổi và trong các thử nghiệm. Ngoài ra, bạn có thể chạy một số phiên bản của ứng dụng hoặc các phần của ứng dụng của bạn song song (tốt cho thử nghiệm!). Cuối cùng, người quản lý của bạn sẽ chết với đối tượng sở hữu của nó (lớp khởi động).

Vì vậy, cấu trúc ứng dụng của bạn giống như một cái cây, nơi những thứ ở trên sở hữu mọi thứ được sử dụng bên dưới chúng. Đừng triển khai một ứng dụng như lưới, nơi mọi người biết mọi người và tìm thấy nhau thông qua các phương pháp toàn cầu.

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.