Làm thế nào tôi có thể tránh có nhiều singletons trong kiến ​​trúc trò chơi của mình?


55

Tôi sử dụng công cụ trò chơi cocos2d-x để tạo trò chơi. Động cơ đã sử dụng nhiều singletons. Nếu ai đó đã sử dụng nó, thì họ nên làm quen với một số trong số họ:

Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault

và nhiều hơn nữa với tổng thể khoảng 16 lớp. Bạn có thể tìm thấy một danh sách tương tự trên trang này: Các đối tượng Singleton trong Cocos2d-html5 v3.0 Nhưng khi tôi muốn viết trò chơi, tôi cần nhiều singletons hơn:

PlayerData (score, lives, ...)
PlayerProgress (passed levels, stars)
LevelData (parameters per levels and level packs)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
GameData (you may obtain some data from server to configure the game)
IAP (for in purchases)
Ads (for showing ads)
Analytics (for collecting some analytics)
EntityComponentSystemManager (mananges entity creation and manipulation)
Box2dManager (manages the physics world)
.....

Tại sao tôi nghĩ họ nên là người độc thân? Bởi vì tôi sẽ cần chúng ở những nơi rất khác nhau trong trò chơi của mình và truy cập được chia sẻ sẽ rất tiện dụng. Nói cách khác, tôi không tạo ra chúng ở đâu đó và chuyển con trỏ xuống tất cả kiến ​​trúc của tôi vì nó sẽ rất khó. Ngoài ra đây là những thứ tôi chỉ cần một. Trong mọi trường hợp tôi sẽ cần một số, tôi cũng có thể sử dụng mẫu Multiton. Nhưng điều tồi tệ nhất là Singleton là mẫu bị chỉ trích nặng nề nhất vì:

 - bad testability
 - no inheritance available
 - no lifetime control
 - no explicit dependency chain
 - global access (the same as global variables, actually)
 - ....

Bạn có thể tìm thấy một số suy nghĩ ở đây: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletonshttps://stackoverflow.com/questions/4074154/when-should-the-singleton -potype-không-được-sử dụng-bên cạnh-rõ ràng

Vì vậy, tôi nghĩ rằng, tôi đang làm điều gì đó sai. Tôi nghĩ rằng của tôi có mùi . :) Tôi làm thế nào nhiều nhà phát triển trò chơi có kinh nghiệm hơn giải quyết vấn đề kiến ​​trúc này? Tôi muốn kiểm tra, có lẽ vẫn còn bình thường trong phát triển trò chơi khi có hơn 30 singletons , được coi là những cái đã có trong công cụ trò chơi.

Tôi đã nghĩ sử dụng Singleton-Facade sẽ có các phiên bản của tất cả các lớp tôi cần, nhưng mỗi lớp sẽ không phải là singletons. Điều này sẽ loại bỏ rất nhiều vấn đề và tôi sẽ chỉ có một người duy nhất là Mặt tiền. Nhưng trong trường hợp này tôi sẽ có một vấn đề thiết kế khác. Mặt tiền sẽ trở thành một ĐỐI TƯỢNG THIÊN CHÚA. Tôi nghĩ rằng mùi này quá . Vì vậy, tôi không thể tìm thấy một giải pháp thiết kế tốt cho tình huống này. Vui lòng cho lời khuyên.


26
Hầu hết mọi người tranh luận với Singleton dường như không nhận ra rằng công việc cần thiết để truyền bá một số dữ liệu / chức năng thông qua tất cả các mã có thể là một nguồn lỗi nhiều hơn là sử dụng Singleton. Vì vậy, về cơ bản họ không so sánh, chỉ từ chối ==> tôi nghi ngờ chủ nghĩa chống Singleton là một tư thế :-) Vì vậy, Singleton có những sai sót, vì vậy nếu bạn có thể tránh nó, hãy làm điều đó để bạn tránh những vấn đề đi kèm , bây giờ nếu bạn không thể, hãy tìm nó mà không cần nhìn lại. Rốt cuộc, Cocos2d dường như hoạt động khá tốt với 16 Singletons của nó ...
GameAlchemist

6
Xin nhắc lại, đây là một câu hỏi về cách thực hiện một nhiệm vụ cụ thể. Đây không phải là một câu hỏi yêu cầu một cuộc thảo luận pro / con về singletons (dù sao cũng sẽ lạc đề ở đây). Hơn nữa, hãy nhớ rằng các bình luận nên được sử dụng để yêu cầu làm rõ từ người hỏi, chứ không phải là một nền tảng cho một cuộc thảo luận tiếp tuyến về những ưu và nhược điểm của singletons. Nếu bạn muốn cung cấp đầu vào của mình, cách thích hợp để làm như vậy là cung cấp câu trả lời hợp pháp cho câu hỏi , trong đó bạn có thể tiết chế giải pháp của mình bằng lời khuyên cho hoặc chống lại mẫu đơn.
Josh

Tôi cho rằng những singleton này sau đó có thể truy cập toàn cầu? Đó sẽ là một lý do để tránh mô hình đó.
Peter - Tái lập Monica

Câu trả lời:


41

Tôi khởi tạo các dịch vụ của mình trong lớp ứng dụng chính của mình và sau đó chuyển chúng dưới dạng con trỏ tới bất kỳ nhu cầu nào để sử dụng chúng thông qua các hàm tạo hoặc hàm. Điều này hữu ích vì hai lý do.

Một, thứ tự khởi tạo và dọn dẹp rất đơn giản và rõ ràng. Không có cách nào để vô tình khởi tạo một dịch vụ ở một nơi khác như bạn có thể với một singleton.

Hai, rõ ràng ai phụ thuộc vào cái gì. Tôi có thể dễ dàng thấy các lớp cần những dịch vụ gì chỉ từ khai báo lớp.

Bạn có thể nghĩ rằng thật khó chịu khi vượt qua tất cả những vật thể này xung quanh, nhưng nếu hệ thống được thiết kế tốt, nó thực sự không tệ chút nào và làm cho mọi thứ rõ ràng hơn (dù sao đối với tôi).


34
+1. Hơn nữa, "sự khó chịu" của việc phải chuyển các tham chiếu đến các hệ thống xung quanh giúp chống lại sự leo thang phụ thuộc; nếu bạn phải chuyển một cái gì đó cho " mọi thứ " , đó là một dấu hiệu tốt cho thấy bạn có vấn đề khớp nối xấu trong thiết kế của mình và cách này rõ ràng hơn nhiều so với việc truy cập toàn cầu vào mọi nơi từ mọi nơi.
Josh

2
Điều này không thực sự cung cấp một giải pháp ... vì vậy, bạn có lớp chương trình của mình với một loạt các đối tượng đơn lẻ trong đó và bây giờ 10 cấp độ sâu trong mã, trong đó cần một trong những singletons đó ... làm thế nào nó được truyền vào?
Chiến tranh

Wardy: Tại sao họ cần phải là người độc thân? Chắc chắn, chủ yếu là bạn có thể vượt qua một trường hợp cụ thể, nhưng điều khiến một người độc thân trở thành một người độc thân là bạn KHÔNG THỂ sử dụng một trường hợp khác. Với phép tiêm, bạn có thể truyền một thể hiện cho một đối tượng và một đối tượng tiếp theo. Chỉ vì bạn không kết thúc việc đó vì bất kỳ lý do gì, không có nghĩa là bạn không thể.
parar

3
Họ không độc thân. Chúng là các đối tượng thông thường như mọi thứ khác với chủ sở hữu được xác định rõ kiểm soát init / dọn dẹp. Nếu bạn sâu 10 cấp mà không có quyền truy cập, có thể bạn có vấn đề về thiết kế. Không phải tất cả mọi thứ cần hoặc nên có quyền truy cập vào trình kết xuất, âm thanh, và như vậy. Cách này làm cho bạn suy nghĩ về thiết kế của bạn. Là một phần thưởng bổ sung cho những người kiểm tra mã của họ (cái gì ??), kiểm tra đơn vị cũng trở nên dễ dàng hơn nhiều.
megadan

2
Có, tôi nghĩ rằng tiêm phụ thuộc (có hoặc không có khung như Spring) là một giải pháp hoàn hảo để xử lý việc này. Tôi thấy đây là một bài viết tuyệt vời cho những ai tự hỏi làm thế nào để mã cấu trúc tốt hơn để tránh mọi thứ đi khắp mọi nơi: misko.hevery.com/2008/10/21/ trên
megadan

17

Tôi sẽ không thảo luận về sự xấu xa đằng sau những người độc thân vì Internet có thể làm điều đó tốt hơn tôi.

Trong các trò chơi của mình, tôi sử dụng mẫu Trình định vị dịch vụ để tránh có hàng tấn Người quản lý / Người quản lý.

Khái niệm khá là đơn giản. Bạn chỉ có một Singleton hoạt động như giao diện duy nhất để tiếp cận những gì bạn từng sử dụng như Singleton. Thay vì có một vài Singleton bây giờ bạn chỉ có một.

Các cuộc gọi như:

FlyingToasterManager.GetInstance().Toast();
SmokeAlarmManager.GetInstance().Start();

Trông như thế này, sử dụng mẫu Trình định vị dịch vụ:

SvcLocator.FlyingToaster.Toast();
SvcLocator.SmokeAlarm.Start();

Như bạn có thể thấy mọi Singleton được chứa trong Bộ định vị dịch vụ.

Để có một lời giải thích chi tiết hơn, tôi khuyên bạn nên đọc trang này về cách sử dụng Mẫu định vị .

[ EDIT ]: như được nêu trong các bình luận, trang web gameprogrammingpotypes.com cũng chứa một phần rất thú vị trên Singleton .

Tôi hy vọng nó sẽ giúp.


4
Trang web được liên kết đến đây, gameprogrammingpotypes.com, cũng có một chương về Singletons có vẻ phù hợp.
ajp15243

28
Các bộ định vị dịch vụ chỉ là các singletons chuyển tất cả các vấn đề bình thường sang thời gian chạy chứ không phải là compXLime. Tuy nhiên, những gì bạn đang đề xuất chỉ là một sổ đăng ký tĩnh khổng lồ, gần như tốt hơn, nhưng về cơ bản vẫn là một khối các biến toàn cục. Đây đơn giản là vấn đề kiến ​​trúc xấu nếu nhiều cấp độ cần truy cập vào cùng một đối tượng. Tiêm phụ thuộc đúng cách là những gì cần thiết ở đây, không được đặt tên khác trên toàn cầu so với toàn cầu hiện tại.
Magus

2
Bạn có thể giải thích về "tiêm phụ thuộc thích hợp" không?
sư màu xanh

17
Điều này không thực sự giúp vấn đề. Về cơ bản, nó có rất nhiều singletons được hợp nhất thành một singleton khổng lồ. Không có vấn đề cấu trúc cơ bản nào được khắc phục hoặc thậm chí được giải quyết, mã vẫn còn hôi thối hơn bây giờ.
ClassicThunder

4
@ classicthunder Mặc dù điều này không giải quyết được mọi vấn đề, đây là một cải tiến lớn so với Singletons vì nó giải quyết được một số vấn đề. Cụ thể, mã bạn viết không còn được ghép với một lớp duy nhất, mà là một giao diện / danh mục các lớp. Bây giờ bạn có thể dễ dàng trao đổi các triển khai khác nhau của các dịch vụ khác nhau.
jhocking

12

Đọc tất cả những câu trả lời, bình luận và bài viết đã chỉ ra, đặc biệt là hai bài viết tuyệt vời này,

cuối cùng, tôi đã đi đến kết luận sau đây, đây là một câu trả lời cho câu hỏi của riêng tôi. Cách tiếp cận tốt nhất là không được lười biếng và vượt qua sự phụ thuộc trực tiếp. Điều này là rõ ràng, có thể kiểm tra, không có quyền truy cập toàn cầu, có thể chỉ ra thiết kế sai nếu bạn phải vượt qua các đại biểu rất sâu và ở nhiều nơi, v.v. Nhưng trong lập trình một kích thước không phù hợp với tất cả. Vì vậy, vẫn có trường hợp không tiện để vượt qua chúng trực tiếp. Ví dụ: trong trường hợp chúng tôi tạo khung trò chơi (mẫu mã sẽ được sử dụng lại làm mã cơ sở để phát triển các loại trò chơi khác nhau) và bao gồm rất nhiều dịch vụ. Ở đây chúng tôi không biết mỗi dịch vụ có thể được thông qua sâu đến mức nào và cần bao nhiêu nơi. Nó phụ thuộc vào một trò chơi cụ thể. Vậy chúng ta phải làm gì trong trường hợp này? Chúng tôi sử dụng mẫu thiết kế Bộ định vị dịch vụ thay vì Singleton,

  1. Bạn không lập trình cho việc triển khai mà đến các giao diện. Điều này rất cần thiết về mặt hiệu trưởng OOP. Trong thời gian chạy, bạn có thể thay đổi hoặc thậm chí vô hiệu hóa dịch vụ bằng cách sử dụng mẫu Null Object. Điều này không chỉ quan trọng về tính linh hoạt, mà nó còn trực tiếp giúp kiểm tra. Bạn có thể vô hiệu hóa, giả, bạn cũng có thể sử dụng mẫu Trang trí, để thêm một số ghi nhật ký và các chức năng khác. Đây là tài sản rất quan trọng, mà Singleton không có.
  2. Một lợi thế rất lớn là bạn có thể kiểm soát tuổi thọ của đối tượng. Trong Singleton, bạn chỉ kiểm soát thời gian đối tượng sẽ được sinh ra bằng mẫu Khởi tạo lười biếng. Nhưng một khi đối tượng được sinh ra, nó sẽ sống cho đến khi kết thúc chương trình. Nhưng trong một số trường hợp, bạn muốn thay đổi dịch vụ. Hoặc thậm chí tắt nó đi. Đặc biệt trong chơi game sự linh hoạt này là rất quan trọng.
  3. Nó kết hợp / kết hợp một số Singletons thành một API nhỏ gọn. Tôi nghĩ rằng bạn cũng có thể sử dụng một số Mặt tiền như Bộ định vị dịch vụ, cho một hoạt động có thể sử dụng một số dịch vụ cùng một lúc.
  4. Bạn có thể lập luận rằng chúng tôi vẫn có quyền truy cập toàn cầu, đó là vấn đề thiết kế chính với Singleton. Tôi đồng ý, nhưng chúng tôi sẽ chỉ cần mẫu này và chỉ khi chúng tôi muốn một số quyền truy cập toàn cầu. Chúng ta không nên phàn nàn về quyền truy cập toàn cầu, nếu chúng ta nghĩ rằng tiêm phụ thuộc trực tiếp không có ích cho tình huống của chúng ta và chúng ta cần có quyền truy cập toàn cầu. Nhưng ngay cả đối với vấn đề này, một cải tiến có sẵn (xem bài viết thứ hai ở trên). Bạn có thể đặt tất cả các dịch vụ ở chế độ riêng tư và bạn có thể kế thừa từ lớp Trình định vị dịch vụ tất cả các lớp mà bạn nghĩ rằng chúng nên sử dụng các dịch vụ. Điều này có thể thu hẹp đáng kể quyền truy cập. Và hãy xem xét rằng trong trường hợp Singleton, bạn không thể sử dụng tính kế thừa vì hàm tạo riêng.

Ngoài ra chúng ta nên xem xét rằng mô hình này đã được sử dụng trong Unity, LibGDX, XNA. Đây không phải là một lợi thế, nhưng đây là một bằng chứng về khả năng sử dụng của mẫu. Hãy xem xét rằng các động cơ này được phát triển bởi nhiều nhà phát triển thông minh trong một thời gian dài và trong các giai đoạn tiến hóa của động cơ, họ vẫn không tìm thấy giải pháp nào tốt hơn.

Tóm lại, tôi nghĩ Bộ định vị dịch vụ có thể rất hữu ích cho các tình huống được mô tả trong câu hỏi của tôi, nhưng vẫn nên tránh nếu có thể. Như một quy tắc của ngón tay cái - sử dụng nó nếu bạn phải.

EDIT: Sau một năm làm việc và sử dụng DI trực tiếp và gặp vấn đề với các nhà xây dựng lớn và rất nhiều đoàn, tôi đã thực hiện nhiều nghiên cứu hơn và đã tìm thấy một bài viết hay nói về ưu và nhược điểm về phương pháp DI và Service Locator. Trích dẫn bài viết này ( http://www.martinfowler.com/articles/injection.html ) của Martin Fowler giải thích khi thực sự các trình định vị dịch vụ là xấu:

Vì vậy, vấn đề chính là dành cho những người đang viết mã dự kiến ​​sẽ được sử dụng trong các ứng dụng ngoài tầm kiểm soát của người viết. Trong những trường hợp này, ngay cả một giả định tối thiểu về Bộ định vị dịch vụ cũng là một vấn đề.

Nhưng dù sao, nếu bạn muốn chúng tôi DI lần đầu tiên, bạn nên đọc http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ bài viết (được chỉ ra bởi @megadan) , bằng cách chú ý rất cẩn thận đến Luật Demeter (LoD) hoặc nguyên tắc ít kiến ​​thức nhất .


Sự không khoan dung cá nhân của tôi đối với người định vị dịch vụ tăng lên chủ yếu từ những nỗ lực của tôi để kiểm tra mã sử dụng chúng. Để kiểm tra hộp đen, bạn gọi hàm tạo để tạo một thể hiện để kiểm tra. Một ngoại lệ có thể được ném ngay lập tức hoặc trên bất kỳ lệnh gọi phương thức nào và có thể không có thuộc tính công khai nào dẫn bạn đến các phụ thuộc bị thiếu. Điều này có thể là nhỏ nếu bạn có quyền truy cập vào nguồn, nhưng nó vẫn vi phạm Nguyên tắc bất ngờ tối thiểu (một vấn đề thường gặp với Singletons). Bạn đã đề nghị chuyển qua bộ định vị dịch vụ, điều này làm giảm bớt một phần của điều này và sẽ được khen thưởng cho nó.
Magus

3

Không có gì lạ khi các bộ phận của cơ sở mã được coi là đối tượng nền tảng hoặc lớp nền tảng, nhưng điều đó không biện minh cho vòng đời của nó được quy định là Singleton.

Các lập trình viên thường dựa vào mô hình Singleton như một phương tiện thuận tiện và lười biếng thuần túy hơn là sử dụng cách tiếp cận thay thế và trở nên dài dòng hơn và áp đặt các mối quan hệ đối tượng giữa nhau trong một trang viên rõ ràng.

Vì vậy, hãy tự hỏi những gì dễ dàng hơn để duy trì.

Nếu bạn giống tôi, tôi thích có thể mở tệp tiêu đề và lướt nhanh qua các đối số của phương thức khởi tạo và phương thức công khai để xem phụ thuộc nào mà đối tượng yêu cầu thay vì phải kiểm tra hàng trăm dòng mã trong tệp triển khai.

Nếu bạn đã tạo một đối tượng thành Singleton và sau đó nhận ra rằng bạn cần có khả năng hỗ trợ nhiều phiên bản của đối tượng đã nói, hãy tưởng tượng các thay đổi sâu rộng cần thiết nếu lớp này là thứ bạn sử dụng khá nhiều trong cơ sở mã của bạn. Cách khác tốt hơn là làm cho các phụ thuộc rõ ràng, ngay cả khi đối tượng chỉ được phân bổ một lần và nếu có nhu cầu hỗ trợ nhiều trường hợp, bạn có thể làm điều đó một cách an toàn mà không cần lo ngại như vậy.

Như những người khác đã chỉ ra, việc sử dụng mẫu Singleton cũng làm xáo trộn khớp nối thường biểu thị thiết kế kém và sự gắn kết cao giữa các mô-đun. Sự gắn kết như vậy thường là không mong muốn và dẫn đến mã giòn có thể khó duy trì.


-1: Câu trả lời này mô tả sai mẫu đơn và tất cả các đối số của nó mô tả các vấn đề với việc triển khai mẫu kém. Singleton đúng một giao diện, và nó tránh mọi vấn đề đơn lẻ mà bạn đã mô tả (bao gồm cả việc chuyển sang không đơn lẻ, đây là một tình huống mà GoF giải quyết rõ ràng). Nếu khó tái cấu trúc việc sử dụng một singleton, thì việc tái cấu trúc việc sử dụng một tham chiếu đối tượng rõ ràng chỉ có thể khó khăn hơn chứ không phải ít hơn. Nếu singleton bằng cách nào đó gây ra việc ghép mã THÊM, đơn giản là nó đã được thực hiện sai.
Seth Battin

1

Singleton là một mô hình nổi tiếng, nhưng thật tốt khi biết mục đích của nó và những ưu và nhược điểm của nó.

Nó thực sự có ý nghĩa nếu không có mối quan hệ nào cả.
Nếu bạn có thể xử lý thành phần của mình với một đối tượng hoàn toàn khác (không phụ thuộc mạnh mẽ) và mong muốn có hành vi tương tự, thì singleton có thể là một lựa chọn tốt.

Mặt khác, nếu bạn cần một chức năng nhỏ , chỉ được sử dụng tại một số thời điểm nhất định, bạn nên ưu tiên chuyển tham chiếu .

Như bạn đã đề cập, singletons không có quyền kiểm soát suốt đời. Nhưng lợi thế là họ có một khởi tạo lười biếng.
Nếu bạn không gọi singleton đó trong vòng đời ứng dụng của mình, nó sẽ không được khởi tạo. Nhưng tôi nghi ngờ bạn xây dựng singletons và không sử dụng chúng.

Bạn phải xem một singleton như một dịch vụ thay vì một thành phần .

Singletons hoạt động, họ không bao giờ phá vỡ bất kỳ ứng dụng. Nhưng sự thiếu sót của họ (bốn điều bạn đã đưa ra) là những lý do bạn sẽ không tạo ra một.


1

Động cơ đã sử dụng nhiều singletons. Nếu ai đó đã sử dụng nó, thì họ nên làm quen với một số trong số họ:

Sự không quen thuộc không phải là lý do tại sao những người độc thân cần phải tránh.

Có nhiều lý do chính đáng tại sao Singleton là cần thiết hoặc không thể tránh khỏi. Các khung trò chơi thường sử dụng các singletons vì đó là hậu quả không thể tránh khỏi khi chỉ có một phần cứng duy nhất. Thật vô nghĩa khi muốn kiểm soát các phần cứng này bằng nhiều phiên bản xử lý tương ứng của chúng. Bề mặt đồ họa là một phần cứng trạng thái bên ngoài và việc vô tình khởi tạo một bản sao thứ hai của hệ thống con đồ họa là không có gì tai hại, vì bây giờ hai hệ thống con đồ họa sẽ chiến đấu với nhau về việc ai sẽ vẽ và khi nào, ghi đè lên nhau không kiểm soát được. Tương tự như vậy với hệ thống xếp hàng sự kiện, họ sẽ chiến đấu về việc ai sẽ nhận được các sự kiện chuột theo kiểu không điều kiện. Khi làm việc với một phần cứng bên ngoài có trạng thái chỉ có một trong số họ, singleton là không thể tránh khỏi để ngăn ngừa xung đột.

Một nơi khác mà Singleton hợp lý là với các trình quản lý Cache. Bộ nhớ cache là một trường hợp đặc biệt. Họ không thực sự được coi là một Singleton, ngay cả khi họ sử dụng tất cả các kỹ thuật tương tự như Singletons để sống và sống mãi mãi. Dịch vụ lưu đệm là dịch vụ trong suốt, chúng không có nghĩa vụ thay đổi hành vi của chương trình, vì vậy nếu bạn thay thế dịch vụ bộ đệm bằng bộ đệm rỗng, chương trình vẫn hoạt động, ngoại trừ việc nó chỉ chạy chậm hơn. Lý do chính tại sao các trình quản lý bộ đệm là ngoại lệ đối với singleton là vì nó không có nghĩa là tắt dịch vụ bộ đệm trước khi tự tắt ứng dụng vì điều đó cũng sẽ loại bỏ các đối tượng được lưu trong bộ nhớ cache, điều này đánh bại điểm có nó như là một đơn lẻ.

Đó là những lý do tốt tại sao các dịch vụ này là singletons. Tuy nhiên, không có lý do chính đáng nào để có singletons áp dụng cho các lớp bạn đã liệt kê.

Bởi vì tôi sẽ cần chúng ở những nơi rất khác nhau trong trò chơi của mình và truy cập được chia sẻ sẽ rất tiện dụng.

Đó không phải là lý do cho singletons. Đó là những lý do cho toàn cầu. Nó cũng là một dấu hiệu của thiết kế kém nếu bạn cần phải vượt qua rất nhiều thứ xung quanh các hệ thống khác nhau. Phải vượt qua mọi thứ xung quanh cho thấy khả năng khớp nối cao có thể được ngăn chặn bằng thiết kế OO tốt.

Chỉ cần nhìn vào danh sách các lớp trong bài đăng của bạn mà bạn nghĩ cần phải là Singletons, tôi có thể nói rằng một nửa trong số họ thực sự không nên là người độc thân và nửa còn lại có vẻ như họ thậm chí không nên ở đó ngay từ đầu. Rằng bạn cần phải vượt qua các đối tượng xung quanh dường như là do thiếu sự đóng gói thích hợp hơn là các trường hợp sử dụng tốt thực sự cho các singletons.

Hãy nhìn vào lớp học của bạn từng chút một:


PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)

Chỉ từ các tên lớp, tôi sẽ nói rằng các lớp này có mùi giống như mô hình phản ứng miền thiếu máu. Các đối tượng thiếu máu thường dẫn đến rất nhiều dữ liệu cần được truyền xung quanh, điều này làm tăng sự ghép đôi và làm cho phần còn lại của mã trở nên cồng kềnh. Ngoài ra, lớp thiếu máu che giấu thực tế rằng bạn có thể vẫn đang suy nghĩ theo thủ tục thay vì sử dụng hướng đối tượng để gói gọn các chi tiết.


IAP (for in purchases)
Ads (for showing ads)

Tại sao các lớp này cần phải là singletons? Đối với tôi, đây có vẻ là những lớp học ngắn, nên được đưa lên khi cần thiết và giải mã khi người dùng kết thúc mua hàng hoặc khi quảng cáo không còn cần phải hiển thị nữa.


EntityComponentSystemManager (mananges entity creation and manipulation)

Nói cách khác, một constructor và một lớp dịch vụ? Tại sao lớp này không có ranh giới rõ ràng cũng không có mục đích ngay cả ở nơi đầu tiên?


PlayerProgress (passed levels, stars)

Tại sao tiến trình của người chơi tách biệt với lớp Người chơi? Lớp Người chơi nên biết cách theo dõi tiến trình của chính mình, nếu bạn muốn triển khai theo dõi tiến trình trong một lớp khác với lớp Người chơi để phân tách trách nhiệm, thì PlayerProTHER phải đứng sau Người chơi.


Box2dManager (manages the physics world)

Tôi thực sự không thể bình luận gì thêm về cái này mà không biết lớp này thực sự làm gì, nhưng có một điều rõ ràng là lớp này được đặt tên kém.


Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)

Đây dường như là các lớp duy nhất mà Singleton có thể hợp lý, bởi vì các đối tượng kết nối xã hội không thực sự là đối tượng của bạn. Các kết nối xã hội chỉ là một cái bóng của các đối tượng bên ngoài sống trong một dịch vụ từ xa. Nói cách khác, đây là một loại bộ đệm. Chỉ cần đảm bảo rằng bạn tách biệt rõ ràng phần bộ nhớ đệm với phần dịch vụ của dịch vụ bên ngoài, vì phần sau không cần phải là một đơn.

Một cách bạn có thể tránh vượt qua các thể hiện của các lớp này là sử dụng chuyển tin nhắn. Thay vì thực hiện cuộc gọi trực tiếp đến các phiên bản của các lớp này, thay vào đó, bạn gửi một tin nhắn được gửi đến dịch vụ Phân tích và Kết nối xã hội một cách không đồng bộ và các dịch vụ này được đăng ký để nhận các tin nhắn này và hành động theo tin nhắn. Vì hàng đợi sự kiện đã là một singleton, bằng cách thực hiện cuộc gọi, bạn sẽ không cần phải vượt qua một trường hợp thực tế khi giao tiếp với một singleton.


-1

Singletons với tôi là LUÔN.

Trong kiến ​​trúc của mình, tôi đã tạo ra một bộ sưu tập GameService mà lớp trò chơi quản lý. Tôi có thể tìm kiếm thêm và xóa khỏi bộ sưu tập này theo yêu cầu.

Bằng cách nói một cái gì đó nằm trong phạm vi toàn cầu, về cơ bản, bạn đang nói "hting này là thần và không có chủ" trong các ví dụ bạn cung cấp ở trên, tôi sẽ nói rằng hầu hết chúng là các lớp trợ giúp hoặc GameService trong trường hợp trò chơi sẽ chịu trách nhiệm cho chúng.

Nhưng đó chỉ là thiết kế của tôi trong động cơ của tôi, tình huống của bạn có thể khác.

Đặt nó trực tiếp hơn ... Người độc thân thực sự duy nhất là trò chơi vì nó sở hữu mọi phần của mã.

Đây là câu trả lời dựa trên bản chất chủ quan của câu hỏi và hoàn toàn dựa trên ý kiến ​​của tôi, nó có thể không đúng cho tất cả các nhà phát triển ngoài kia.

Chỉnh sửa: Theo yêu cầu ...

Ok đây là từ đầu của tôi vì tôi không có mã của mình trước mặt tôi nhưng về cơ bản, gốc rễ của bất kỳ trò chơi nào là lớp Trò chơi thực sự là một đơn ... nó phải vậy!

Vì vậy, tôi đã thêm vào ...

class Game
{
   IList<GameService> Services;

   public T GetService<T>() where T : GameService
   {
       return Services.FirstOrDefatult(s => s is T);
   }
}

Bây giờ tôi cũng có một lớp hệ thống (tĩnh) chứa tất cả các singletons của tôi từ toàn bộ chương trình (chứa rất ít tùy chọn trò chơi và cấu hình và đó là về nó) ...

public static class Sys
{
    // only the main game assembly can set this (done by the game ctor)
    // anything can get
    public static Game Game { get; internal set; }
}

Và sử dụng ...

Từ bất cứ nơi nào tôi có thể làm một cái gì đó như

var tService = Sys.Game.getService<T>();
tService.Something();

Cách tiếp cận này có nghĩa là trò chơi "Sở hữu" trò chơi của tôi thường được coi là "đơn lẻ" và tôi có thể mở rộng lớp GameService cơ bản theo cách tôi muốn. Tôi có các giao diện như IUpdatizable mà trò chơi của tôi kiểm tra và gọi tới bất kỳ dịch vụ nào triển khai nó khi cần cho các tình huống cụ thể.

Tôi cũng có các dịch vụ kế thừa / mở rộng các dịch vụ khác cho các tình huống cảnh cụ thể hơn.

Ví dụ ...

Tôi có một dịch vụ Vật lý xử lý các mô phỏng vật lý của tôi nhưng tùy thuộc vào mô phỏng vật lý cảnh có thể cần một số hành vi hơi khác nhau.


1
Các dịch vụ trò chơi không nên được khởi tạo chỉ một lần? Nếu vậy, nó chỉ là một mẫu singleton không được thi hành ở cấp lớp, điều này tệ nhất so với một mẫu đơn được triển khai đúng cách.
GameAlchemist

2
@Wardy Tôi không hiểu rõ những gì bạn đã làm nhưng có vẻ như Singleton-Facade tôi đã mô tả và nó sẽ trở thành một đối tượng GOD, phải không?
Narek

10
Nó chỉ là một mô hình Singleton được thực hiện ở cấp độ trò chơi. Vì vậy, về cơ bản khía cạnh thú vị nhất của nó là cho phép bạn giả vờ rằng bạn không sử dụng Singleton. Hữu ích nếu ông chủ của bạn đến từ nhà thờ chống mẫu.
GameAlchemist

2
Tôi không chắc cách này hiệu quả khác với việc sử dụng Singletons. Không phải là sự khác biệt duy nhất trong cách bạn truy cập cụ thể vào dịch vụ hiện có của loại này sao? Tức là var tService = Sys.Game.getService<T>(); tService.Something();thay vì bỏ qua getServicephần và truy cập trực tiếp?
Christian

1
@Christian: Thật vậy, đó chính xác là những gì antipotype của Trình định vị dịch vụ. Rõ ràng cộng đồng này vẫn nghĩ rằng đó là một mẫu thiết kế được đề xuất.
Magus

-1

Một thành phần thiết yếu của việc loại bỏ singleton mà đối thủ của singleton thường không xem xét là thêm logic để xử lý trạng thái phức tạp hơn nhiều mà các đối tượng gói gọn khi singletons nhường chỗ cho các giá trị cá thể. Thật vậy, ngay cả việc xác định trạng thái của một đối tượng sau khi thay thế một singleton bằng một biến thể hiện có thể khó khăn, nhưng các lập trình viên thay thế singletons bằng các biến thể hiện nên được chuẩn bị để đối phó với nó.

So sánh, ví dụ, Java HashMapvới .NET Dictionary. Cả hai đều khá giống nhau, nhưng chúng có một điểm khác biệt chính: Java HashMapđược mã hóa cứng để sử dụng equalshashCode(nó sử dụng hiệu quả một bộ so sánh đơn) trong khi .NET Dictionarycó thể chấp nhận một thể hiện IEqualityComparer<T>như là một tham số của hàm tạo. Chi phí thời gian chạy của việc giữ IEqualityComparerlà tối thiểu, nhưng sự phức tạp mà nó giới thiệu thì không.

Bởi vì mỗi Dictionarytrường hợp đóng gói một IEqualityComparer, trạng thái của nó không chỉ là ánh xạ giữa các khóa mà nó thực sự chứa và các giá trị liên quan của chúng, mà thay vào đó là ánh xạ giữa các bộ tương đương được xác định bởi các khóa và bộ so sánh và các giá trị liên quan của chúng. Nếu hai trường hợp d1d2các Dictionarytài liệu tham khảo giữ cùng IEqualityComparer, nó sẽ có thể để tạo ra một trường hợp mới d3như vậy mà cho bất kỳ x, d3.ContainsKey(x)sẽ tương đương d1.ContainsKey(x) || d2.ContainsKey(x). Tuy nhiên, nếu d1d2có thể giữ các tham chiếu đến các so sánh đẳng thức tùy ý khác nhau, nói chung sẽ không có cách nào để hợp nhất chúng để đảm bảo điều kiện đó được giữ.

Việc thêm các biến đối tượng trong một nỗ lực để loại bỏ các singletons, nhưng việc không tính đúng cho các trạng thái mới có thể có thể được ngụ ý bởi các biến cá thể đó có thể tạo ra mã kết thúc dễ vỡ hơn so với đơn lẻ. Nếu mỗi phiên bản đối tượng có một trường riêng biệt, nhưng mọi thứ sẽ gặp trục trặc nghiêm trọng trừ khi tất cả chúng đều xác định cùng một thể hiện đối tượng, thì tất cả các trường mới đã mua là một số lượng lớn các cách có thể sai.


1
Mặc dù đây chắc chắn là một khía cạnh thú vị của cuộc thảo luận, tôi không nghĩ rằng nó thực sự giải quyết câu hỏi mà OP thực sự hỏi.
Josh

1
@JoshPetrie: Các ý kiến ​​về bài viết gốc (ví dụ của Magus) cho rằng chi phí thời gian chạy của việc mang theo các tài liệu tham khảo để tránh sử dụng singletons là không tốt. Như vậy, tôi sẽ xem xét các chi phí ngữ nghĩa có liên quan để có mọi đối tượng gói gọn các tài liệu tham khảo cho mục đích tránh các singletons. Người ta nên tránh những người độc thân trong trường hợp họ có thể tránh được một cách rẻ tiền và dễ dàng, nhưng mã không yêu cầu quá đơn lẻ nhưng có thể bị phá vỡ ngay khi nhiều trường hợp của một thứ gì đó tồn tại tồi tệ hơn mã sử dụng một đơn.
supercat

2
Câu trả lời không phải để giải quyết các ý kiến ​​mà họ là để trả lời trực tiếp câu hỏi.
ClassicThunder

@ClassicThunder: Bạn có thích chỉnh sửa tốt hơn không?
supercat
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.