ฉันจะหลีกเลี่ยงการมีซิงเกิลตันมากมายในสถาปัตยกรรมเกมของฉันได้อย่างไร


55

ฉันใช้โปรแกรมเกม cocos2d-x เพื่อสร้างเกม เครื่องยนต์ใช้ซิงเกิลตันหลายตัวอยู่แล้ว หากมีคนใช้มันพวกเขาควรคุ้นเคยกับบางคน:

Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault

และอื่น ๆ อีกมากมายโดยรวมประมาณ 16 ชั้นเรียน คุณสามารถค้นหารายการที่คล้ายกันในหน้านี้: วัตถุ Singleton ใน Cocos2d-html5 v3.0แต่เมื่อฉันต้องการที่จะเขียนฉันเกมฉันต้องการ Singletons มากขึ้น:

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

ทำไมฉันคิดว่าพวกเขาควรจะเป็นโสด? เพราะฉันจะต้องการพวกเขาในสถานที่ที่แตกต่างกันมากในเกมของฉันและการเข้าถึงที่ใช้ร่วมกันจะมีประโยชน์มาก กล่าวอีกนัยหนึ่งฉันไม่ได้สร้างอะไรที่ไหนสักแห่งและส่งพอยน์เตอร์ให้กับสถาปัตยกรรมทั้งหมดของฉันเพราะมันจะยากมาก นอกจากนี้ยังเป็นสิ่งที่ฉันต้องการเพียงหนึ่งเดียว ไม่ว่าในกรณีใดฉันจะต้องใช้หลายแบบฉันสามารถใช้รูปแบบ Multiton ได้เช่นกัน แต่สิ่งที่แย่ที่สุดคือ Singleton เป็นรูปแบบที่ถูกวิพากษ์วิจารณ์อย่างหนักที่สุดเนื่องจาก:

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

คุณสามารถค้นหาความคิดได้ที่นี่: https://stackoverflow.com/questions/137975/what-is-so-so-bad-about-singletonsและhttps://stackoverflow.com/questions/4074154/when-should-the-singleton -pattern ที่ไม่ได้รับการใช้-นอกเหนือจากที่เห็นได้ชัด

ดังนั้นฉันคิดว่าฉันกำลังทำอะไรผิด ฉันคิดว่าฉันรหัสกลิ่น :) ฉันกระทบกระทั่งนักพัฒนาเกมที่มีประสบการณ์มากขึ้นแก้ปัญหาสถาปัตยกรรมนี้ได้อย่างไร ฉันต้องการตรวจสอบบางทีอาจเป็นเรื่องปกติในการพัฒนาเกมที่มีมากกว่า 30 ซิงเกิลตันถือว่าเป็นเกมที่มีอยู่แล้วในเอ็นจิ้นเกม

ฉันคิดว่าจะใช้ Singleton-Facade ซึ่งจะมีอินสแตนซ์ของคลาสเหล่านี้ทั้งหมดที่ฉันต้องการ แต่แต่ละอันก็ไม่ได้เป็นซิงเกิลแล้ว นี่จะกำจัดปัญหามากมายและฉันจะมีเพียงหนึ่งเดียวซึ่งจะเป็นซุ้มเอง แต่ในกรณีนี้ฉันจะมีปัญหาการออกแบบอีก อาคารจะกลายเป็นโครงการของพระเจ้า ฉันคิดว่ามันมีกลิ่นเหมือนกัน ดังนั้นฉันไม่สามารถหาทางออกการออกแบบที่ดีสำหรับสถานการณ์นี้ โปรดให้คำแนะนำ.


26
คนส่วนใหญ่โต้เถียงกับ Singleton ดูเหมือนจะไม่ตระหนักว่างานที่ต้องการเผยแพร่ข้อมูล / ฟังก์ชันการทำงานผ่านรหัสทั้งหมดอาจจะเป็นแหล่งที่มาของบั๊กมากกว่าการใช้ Singleton โดยพื้นฐานแล้วพวกเขาไม่ได้เปรียบเทียบเพียงแค่ปฏิเสธ ==> ฉันสงสัยว่าการต่อต้าน Singletonism เป็นท่า :-) ดังนั้น Singleton มีข้อบกพร่องดังนั้นถ้าคุณสามารถหลีกเลี่ยงได้ให้ทำเพื่อหลีกเลี่ยงปัญหาที่มาพร้อมกับมัน ตอนนี้ถ้าคุณไม่สามารถไปได้โดยไม่ต้องมองย้อนกลับไป ท้ายที่สุดดูเหมือนว่า Cocos2d จะทำงานได้ค่อนข้างดีด้วย 16 Singletons ...
GameAlchemist

6
เพื่อเป็นการเตือนความจำนี่เป็นคำถามเกี่ยวกับวิธีการทำงานบางอย่าง นี่ไม่ใช่คำถามที่ถามถึงการสนทนาแบบ pro / con ของ singletons (ซึ่งจะอยู่นอกหัวข้อที่นี่) นอกจากนี้โปรดจำไว้ว่าควรใช้ความคิดเห็นเพื่อขอคำชี้แจงจากผู้ถามไม่ใช่เป็นเวทีสำหรับการพูดคุยเกี่ยวกับข้อดีและข้อเสียของซิงเกิล หากคุณต้องการให้ข้อมูลของคุณวิธีที่เหมาะสมในการทำเช่นนั้นคือการให้คำตอบที่ถูกต้องสำหรับคำถามซึ่งคุณสามารถให้คำแนะนำสำหรับอารมณ์หรือวิธีการแก้ปัญหาของคุณกับรูปแบบซิงเกิล
Josh

ฉันคิดว่าซิงเกิลเหล่านี้นั้นสามารถเข้าถึงได้ทั่วโลกหรือไม่ ซึ่งจะเป็นเหตุผลหนึ่งที่หลีกเลี่ยงรูปแบบนั้น
ปีเตอร์ - Reinstate Monica

คำตอบ:


41

ฉันเริ่มต้นบริการของฉันในคลาสแอปพลิเคชันหลักของฉันและจากนั้นส่งผ่านเป็นตัวชี้ไปยังสิ่งที่จำเป็นต้องใช้พวกเขาทั้งผ่านตัวสร้างหรือฟังก์ชั่น สิ่งนี้มีประโยชน์ด้วยเหตุผลสองประการ

หนึ่งลำดับของการเริ่มต้นและการล้างข้อมูลนั้นง่ายและชัดเจน ไม่มีวิธีใดในการเริ่มต้นบริการหนึ่งโดยไม่ตั้งใจเช่นที่คุณสามารถทำได้ด้วยซิงเกิล

สองมันชัดเจนว่าใครขึ้นอยู่กับอะไร ฉันสามารถดูว่าคลาสใดที่ต้องการบริการใดจากการประกาศคลาส

คุณอาจคิดว่ามันน่ารำคาญที่จะผ่านวัตถุเหล่านี้ไปรอบ ๆ แต่ถ้าระบบได้รับการออกแบบมาอย่างดีมันก็ไม่เลวเลยและทำให้สิ่งต่าง ๆ ชัดเจนขึ้น (สำหรับฉันแล้ว)


34
+1 นอกจากนี้ "ความน่ารำคาญ" ที่ต้องผ่านการอ้างอิงไปยังระบบรอบ ๆ ช่วยในการต่อต้านการพึ่งพาการคืบ หากคุณต้องส่งบางสิ่งบางอย่างไปที่ " ทุกอย่าง " นั่นเป็นสัญญาณที่ดีที่คุณมีปัญหาการมีเพศสัมพันธ์ที่ไม่ดีในการออกแบบของคุณและมันก็ชัดเจนกว่านี้ด้วยการเข้าถึงทั่วโลกที่สลับซับซ้อนทุกอย่างจากทุกที่
Josh

2
สิ่งนี้ไม่ได้ให้ทางออก ... ดังนั้นคุณมีคลาสโปรแกรมของคุณที่มีวัตถุเดี่ยว ๆ อยู่ในนั้นและตอนนี้โค้ดระดับ 10 ที่ลึกลงไปในฉากที่ต้องการฉากหนึ่งต้องเป็นหนึ่งในนั้น ... มันผ่านไปได้อย่างไร?
สงคราม

Wardy: ทำไมพวกเขาต้องเป็นซิงเกิลตัน? แน่นอนว่าส่วนใหญ่คุณอาจจะผ่านรอบตัวอย่างบางอย่าง แต่สิ่งที่ทำให้คนโสดเป็นคนเดียวคือว่าคุณไม่สามารถใช้อินสแตนซ์ที่แตกต่างกัน ด้วยการฉีดคุณสามารถผ่านหนึ่งอินสแตนซ์สำหรับวัตถุหนึ่งและอีกวัตถุหนึ่งสำหรับถัดไป เพียงเพราะคุณไม่ได้ทำสิ่งนั้นด้วยเหตุผลใด ๆ ก็ตามไม่ได้หมายความว่าคุณทำไม่ได้
parar

3
พวกเขาไม่ใช่คนโสด พวกเขาเป็นวัตถุปกติเหมือนอย่างอื่นกับเจ้าของที่กำหนดไว้อย่างดีที่ควบคุม init / cleanup หากคุณอยู่ลึกลงไป 10 ระดับโดยไม่มีการเข้าถึงบางทีคุณอาจมีปัญหาด้านการออกแบบ ไม่ใช่ทุกสิ่งที่ต้องการหรือควรมีสิทธิ์เข้าถึง renderers เสียงและอื่น ๆ วิธีนี้ทำให้คุณคิดถึงการออกแบบของคุณ ในฐานะโบนัสเพิ่มเติมสำหรับผู้ที่ทดสอบโค้ดของพวกเขา (อะไร ??) การทดสอบหน่วยก็ง่ายขึ้นมากเช่นกัน
megadan

2
ใช่ฉันคิดว่าการฉีดพึ่งพา (มีหรือไม่มีกรอบเช่นฤดูใบไม้ผลิ) เป็นโซลูชั่นที่สมบูรณ์แบบสำหรับการจัดการนี้ ฉันพบว่านี่เป็นบทความที่ยอดเยี่ยมสำหรับผู้ที่สงสัยว่าจะจัดโครงสร้างรหัสได้อย่างไรเพื่อหลีกเลี่ยงการส่งผ่านทุกที่: misko.hevery.com/2008/10/21/…
megadan

17

ฉันจะไม่พูดคุยเกี่ยวกับความชั่วร้ายที่อยู่เบื้องหลังซิงเกิลเพราะอินเทอร์เน็ตสามารถทำได้ดีกว่าฉัน

ในเกมของฉันฉันใช้รูปแบบของตัวค้นหาบริการเพื่อหลีกเลี่ยงการมี Singletons / ผู้จัดการจำนวนมาก

แนวคิดนี้ค่อนข้างเรียบง่าย คุณมี Singleton เพียงตัวเดียวที่ทำหน้าที่เหมือนอินเทอร์เฟซเดียวเพื่อเข้าถึงสิ่งที่คุณเคยใช้เป็น Singleton แทนที่จะมี Singleton หลายตัวตอนนี้คุณมีเพียงอันเดียว

โทรชอบ:

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

ดูเหมือนว่านี้โดยใช้รูปแบบตัวระบุบริการ:

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

อย่างที่คุณเห็นทุก Singleton มีอยู่ใน Service Locator

หากต้องการคำอธิบายโดยละเอียดเพิ่มเติมฉันแนะนำให้คุณอ่านหน้านี้เกี่ยวกับวิธีใช้รูปแบบตัวระบุตำแหน่ง

[ แก้ไข ]: เป็นแหลมในความคิดเห็น gameprogrammingpatterns.com เว็บไซต์นอกจากนี้ยังมีส่วนที่น่าสนใจมากในซิงเกิล

ฉันหวังว่ามันจะช่วย


4
เว็บไซต์ที่เชื่อมโยงกับที่นี่ gameprogrammingpatterns.com ยังมีบทเกี่ยวกับ Singletonsที่ดูเหมือนว่าเกี่ยวข้อง
ajp15243

28
ตัวระบุตำแหน่งบริการเป็นเพียงซิงเกิลตันที่จะย้ายปัญหาปกติทั้งหมดไปยังรันไทม์แทนที่จะเป็นเวลารวบรวม อย่างไรก็ตามสิ่งที่คุณแนะนำคือเพียงแค่รีจีสทรีแบบสถิตขนาดใหญ่ซึ่งเกือบจะดีกว่า แต่ก็ยังคงมีตัวแปรระดับโลกจำนวนมาก นี่เป็นเพียงเรื่องของสถาปัตยกรรมที่ไม่ดีหากหลายระดับต้องการเข้าถึงวัตถุเดียวกัน การฉีดขึ้นต่อกันที่เหมาะสมนั้นเป็นสิ่งที่ต้องการในที่นี้ไม่ใช่ชื่อ globals แตกต่างจาก globals ปัจจุบัน
Magus

2
คุณสามารถอธิบายรายละเอียดเกี่ยวกับ "การฉีดพึ่งพาอย่างเหมาะสม" ได้หรือไม่?
Blue Wizard

17
สิ่งนี้ไม่ได้ช่วยแก้ไขปัญหาจริงๆ มันเป็นสิ่งสำคัญมากที่รวมอยู่ในซิงเกิลยักษ์หนึ่งตัว ไม่มีปัญหาโครงสร้างพื้นฐานที่ได้รับการแก้ไขหรือแก้ไขแม้แต่รหัสยังคงเหม็นเพียงที่น่าสนใจในขณะนี้
ClassicThunder

4
@classicthunder แม้ว่านี่จะไม่ได้แก้ปัญหาทุกเรื่อง แต่นี่เป็นการปรับปรุงครั้งใหญ่ของ Singletons เพราะมันแก้ไขปัญหาได้หลายอย่าง โดยเฉพาะอย่างยิ่งรหัสที่คุณเขียนไม่ได้อยู่คู่กับคลาสเดี่ยวอีกต่อไป แต่จะเป็นอินเตอร์เฟส / หมวดหมู่ของคลาส ตอนนี้คุณสามารถสลับการใช้งานที่แตกต่างกันของบริการต่างๆ
jhocking

12

การอ่านคำตอบความคิดเห็นและบทความเหล่านี้ทั้งหมดชี้ให้เห็นโดยเฉพาะบทความที่ยอดเยี่ยมทั้งสองนี้

ในที่สุดฉันก็มาถึงข้อสรุปต่อไปนี้ซึ่งเป็นคำตอบสำหรับคำถามของฉันเอง วิธีที่ดีที่สุดคือไม่ต้องขี้เกียจและผ่านการพึ่งพาโดยตรง นี่คือชัดเจนทดสอบได้ไม่มีการเข้าถึงทั่วโลกสามารถบ่งบอกถึงการออกแบบที่ผิดถ้าคุณต้องส่งผู้แทนลึกมากและในหลาย ๆ สถานที่และอื่น ๆ แต่ในการเขียนโปรแกรมหนึ่งขนาดไม่พอดีทั้งหมด ดังนั้นยังมีกรณีที่ไม่สะดวกในการส่งโดยตรง ตัวอย่างเช่นในกรณีที่เราสร้างเฟรมเวิร์กเกม (เทมเพลตรหัสซึ่งจะถูกใช้ซ้ำเป็นรหัสฐานเพื่อพัฒนาเกมประเภทต่าง ๆ ) และรวมถึงบริการต่างๆมากมาย ที่นี่เราไม่ทราบว่าสามารถให้บริการแต่ละบริการได้ลึกเพียงใดและจำเป็นต้องใช้บริการกี่แห่ง มันขึ้นอยู่กับเกมนั้น ๆ แล้วเราจะทำอย่างไรในกรณีเช่นนี้? เราใช้รูปแบบการออกแบบ Locator บริการแทนซิงเกิล

  1. คุณไม่ได้ตั้งโปรแกรมให้กับการติดตั้งใช้งาน แต่ไปที่อินเตอร์เฟส นี่เป็นสิ่งที่สำคัญมากในแง่ของหลักการ OOP ที่รันไทม์คุณสามารถเปลี่ยนหรือปิดใช้งานบริการโดยใช้รูปแบบวัตถุ Null สิ่งนี้ไม่เพียงมีความสำคัญในแง่ของความยืดหยุ่นเท่านั้น แต่ยังช่วยในการทดสอบโดยตรง คุณสามารถปิดการใช้งานจำลองนอกจากนี้คุณสามารถใช้รูปแบบของ Decorator เพื่อเพิ่มการบันทึกและฟังก์ชั่นอื่น ๆ ได้เช่นกัน นี่เป็นคุณสมบัติที่สำคัญมากซึ่ง Singleton ไม่มี
  2. ข้อดีอีกอย่างคือคุณสามารถควบคุมอายุการใช้งานของวัตถุได้ ใน Singleton คุณสามารถควบคุมเวลาที่วัตถุจะถูกวางไข่ด้วยรูปแบบการเริ่มต้น Lazy แต่เมื่อวัตถุเกิดใหม่มันจะยังคงอยู่จนกว่าจะสิ้นสุดโปรแกรม แต่ในบางกรณีคุณต้องการเปลี่ยนบริการ หรือแม้แต่ปิดมันลง โดยเฉพาะอย่างยิ่งในการเล่นเกมความยืดหยุ่นนี้เป็นสิ่งสำคัญมาก
  3. มันรวม / ตัด Singletons หลายรายการไว้ใน API ขนาดกะทัดรัดหนึ่งรายการ ฉันคิดว่าคุณอาจใช้ Facade บางอย่างเช่น Service Locators ซึ่งสำหรับการดำเนินการครั้งเดียวอาจใช้บริการหลายอย่างพร้อมกัน
  4. คุณอาจยืนยันว่าเรายังคงมีการเข้าถึงทั่วโลกซึ่งเป็นปัญหาการออกแบบหลักของ Singleton ฉันเห็นด้วย แต่เราจะต้องใช้รูปแบบนี้เท่านั้นและถ้าเราต้องการการเข้าถึงระดับโลก เราไม่ควรบ่นเกี่ยวกับการเข้าถึงทั่วโลกถ้าเราคิดว่าการฉีดการพึ่งพาโดยตรงนั้นไม่สะดวกสำหรับสถานการณ์ของเราและเราต้องการการเข้าถึงระดับโลก แต่ถึงแม้จะมีปัญหานี้การปรับปรุงก็มีให้ (ดูบทความที่สองด้านบน) คุณสามารถทำให้บริการทั้งหมดเป็นส่วนตัวและคุณสามารถสืบทอดจากคลาสของตัวระบุตำแหน่งบริการทุกชั้นที่คุณคิดว่าพวกเขาควรใช้บริการ นี่จะทำให้การเข้าถึงแคบลงอย่างมาก และโปรดพิจารณาว่าในกรณีของซิงเกิลคุณไม่สามารถใช้การสืบทอดเนื่องจากคอนสตรัคเตอร์ส่วนตัว

นอกจากนี้เราควรพิจารณาว่ารูปแบบนี้ใช้ใน Unity, LibGDX, XNA นี่ไม่ใช่ข้อได้เปรียบ แต่เป็นข้อพิสูจน์ถึงความสามารถในการใช้รูปแบบ พิจารณาว่าเครื่องยนต์เหล่านี้ได้รับการพัฒนาโดยนักพัฒนาอัจฉริยะจำนวนมากมาเป็นเวลานานและในช่วงวิวัฒนาการของเครื่องยนต์พวกเขายังไม่พบทางออกที่ดีกว่า

โดยสรุปฉันคิดว่า Service Locator มีประโยชน์มากสำหรับสถานการณ์ที่อธิบายไว้ในคำถามของฉัน แต่ก็ควรหลีกเลี่ยงหากเป็นไปได้ ตามกฎของหัวแม่มือ - ใช้มันถ้าคุณต้อง

แก้ไข: หลังจากหนึ่งปีของการทำงานและใช้ DI โดยตรงและมีปัญหากับตัวสร้างขนาดใหญ่และผู้แทนจำนวนมากฉันได้ทำการวิจัยมากขึ้นและได้พบบทความที่ดีที่พูดถึงข้อดีข้อเสียของ DI และ Service Locator อ้างถึงบทความนี้ ( http://www.martinfowler.com/articles/inject.html ) โดย Martin Fowler ที่อธิบายเมื่อจริง ๆ แล้วตัวระบุบริการไม่ถูกต้อง:

ดังนั้นประเด็นหลักคือสำหรับผู้ที่กำลังเขียนโค้ดที่คาดว่าจะใช้ในแอปพลิเคชันนอกเหนือจากการควบคุมของนักเขียน ในกรณีเหล่านี้แม้แต่ข้อสันนิษฐานเล็กน้อยเกี่ยวกับ Service Locator ก็เป็นปัญหา

แต่อย่างไรก็ตามถ้าคุณต้องการให้เราเป็นครั้งแรกคุณควรอ่านhttp://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/บทความนี้ (ชี้ให้เห็นโดย @megadan) โดยให้ความสนใจระมัดระวังเป็นอย่างมากกับกฎหมายของ Demeter (อร) หรือหลักการของความรู้น้อย


การแพ้ส่วนตัวของฉันที่มีต่อตัวระบุตำแหน่งผู้ให้บริการส่วนใหญ่มาจากความพยายามของฉันในการทดสอบรหัสซึ่งใช้พวกเขา ในความสนใจของการทดสอบกล่องดำคุณเรียกตัวสร้างเพื่อให้เป็นตัวอย่างในการทดสอบ อาจมีข้อยกเว้นเกิดขึ้นทันทีหรือในการเรียกใช้เมธอดและอาจไม่มีคุณสมบัติสาธารณะที่จะนำคุณไปสู่การพึ่งพาที่ขาดหายไป สิ่งนี้อาจน้อยหากคุณเข้าถึงแหล่งข้อมูล แต่ก็ยังคงละเมิดหลักการของความประหลาดใจน้อยที่สุด (ปัญหาที่เกิดขึ้นบ่อยกับ Singletons) คุณได้แนะนำให้ส่งผ่านตัวระบุตำแหน่งบริการซึ่งจะช่วยบรรเทาส่วนหนึ่งของเรื่องนี้และควรได้รับคำชม
Magus

3

ไม่ใช่เรื่องแปลกสำหรับบางส่วนของฐานรหัสที่จะพิจารณาว่าเป็นรากฐานที่สำคัญหรือคลาสพื้นฐาน แต่ไม่ได้พิสูจน์ให้เห็นว่าวงจรชีวิตจะต้องกำหนดเป็นซิงเกิล

โปรแกรมเมอร์มักใช้รูปแบบซิงเกิลเป็นวิธีการอำนวยความสะดวกและความเกียจคร้านบริสุทธิ์มากกว่าการใช้วิธีอื่นและเป็นความสัมพันธ์ทางวัตถุระหว่างกันในคฤหาสน์ที่ชัดเจนยิ่งขึ้น

ดังนั้นจงถามตัวคุณเองว่าการดูแลรักษาแบบไหนดีกว่า

หากคุณชอบฉันฉันชอบที่จะสามารถเปิดไฟล์ส่วนหัวและสั้น ๆ ผ่านข้อโต้แย้งคอนสตรัคเตอร์และวิธีการสาธารณะเพื่อดูว่าการพึ่งพาวัตถุต้องการอะไรมากกว่าที่จะต้องตรวจสอบโค้ดหลายร้อยบรรทัดในไฟล์การใช้งาน

หากคุณทำวัตถุเป็นซิงเกิลและต่อมาก็ตระหนักว่าคุณจำเป็นต้องสามารถรองรับวัตถุดังกล่าวได้หลายอินสแตนซ์ให้จินตนาการถึงการเปลี่ยนแปลงกวาดที่จำเป็นถ้าคลาสนี้เป็นสิ่งที่คุณใช้อย่างหนักตลอดทั้งฐานรหัสของคุณ ทางเลือกที่ดีกว่าคือการทำให้การอ้างอิงชัดเจนแม้ว่าวัตถุจะถูกจัดสรรเพียงครั้งเดียวและหากจำเป็นต้องเกิดขึ้นเพื่อรองรับหลายอินสแตนซ์คุณสามารถทำได้อย่างปลอดภัยโดยไม่ต้องกังวล

ดังที่คนอื่น ๆ ชี้ให้เห็นการใช้รูปแบบซิงเกิลยังทำให้งงงวยการมีเพศสัมพันธ์ที่มักจะหมายถึงการออกแบบที่ไม่ดีและการทำงานร่วมกันสูงระหว่างโมดูล การทำงานร่วมกันแบบนี้มักจะไม่พึงประสงค์และนำไปสู่การโค้ดที่เปราะบางซึ่งยากต่อการบำรุงรักษา


-1: คำตอบนี้อธิบายรูปแบบของซิงเกิลอย่างไม่ถูกต้องและข้อโต้แย้งทั้งหมดอธิบายถึงปัญหาของการใช้รูปแบบที่ไม่ดี ซิงเกิลที่เหมาะสมเป็นอินเทอร์เฟซและจะหลีกเลี่ยงปัญหาทุกข้อที่คุณอธิบาย (รวมถึงการเปลี่ยนไปสู่การไม่พูดเดี่ยวซึ่งเป็นสถานการณ์ที่ GoF กล่าวอย่างชัดเจน) หากเป็นการยากที่จะสร้างการใช้งาน singleton อีกครั้งการใช้การอ้างอิงวัตถุที่ชัดเจนจะทำได้ยากขึ้นไม่น้อย หากซิงเกิลทำให้เกิดการเชื่อมต่อของรหัสเพิ่มเติมมันก็ทำผิดไป
เซทแบ

1

Singleton เป็นรูปแบบที่มีชื่อเสียง แต่ก็เป็นการดีที่ได้ทราบถึงวัตถุประสงค์ที่ใช้และข้อดีและข้อเสีย

มันสมเหตุสมผลจริง ๆ ถ้าไม่มีความสัมพันธ์เลย
หากคุณสามารถจัดการองค์ประกอบของคุณด้วยวัตถุที่แตกต่างกันโดยสิ้นเชิง(ไม่มีการพึ่งพาที่แข็งแกร่ง)และคาดว่าจะมีพฤติกรรมเดียวกันซิงเกิลอาจเป็นตัวเลือกที่ดี

ในทางตรงกันข้ามถ้าคุณจำเป็นต้องมีฟังก์ชั่นขนาดเล็กที่ใช้เฉพาะในบางช่วงเวลาที่คุณควรต้องการอ้างอิงผ่าน

อย่างที่คุณพูดถึงซิงเกิลตันไม่มีการควบคุมตลอดชีวิต แต่ข้อดีคือมีการเริ่มต้นที่ขี้เกียจ
หากคุณไม่เรียกว่าซิงเกิลนั้นตลอดช่วงอายุการใช้งานแอปพลิเคชันของคุณจะไม่ถูกยกตัวอย่าง แต่ฉันสงสัยว่าคุณสร้างซิงเกิลตันและไม่ใช้มัน

คุณต้องดูเดี่ยวเช่นบริการแทนที่จะเป็นส่วนประกอบ

ทำงานเดี่ยวพวกเขาไม่เคยทำลายโปรแกรมใด ๆ แต่การขาดของพวกเขา(ทั้งสี่ที่คุณให้)เป็นเหตุผลที่คุณจะไม่ทำ


1

เครื่องยนต์ใช้ซิงเกิลตันหลายตัวอยู่แล้ว หากมีคนใช้มันพวกเขาควรคุ้นเคยกับบางคน:

ความไม่คุ้นเคยไม่ใช่เหตุผลว่าทำไมจึงต้องหลีกเลี่ยงซิงเกิลตัน

มีหลายเหตุผลที่ดีว่าทำไมซิงเกิลตันจึงจำเป็นหรือหลีกเลี่ยงไม่ได้ เฟรมเวิร์กเกมมักใช้ซิงเกิลตันเนื่องจากเป็นผลมาจากการมีฮาร์ดแวร์เพียงชิ้นเดียว มันไม่สมเหตุสมผลเลยที่จะต้องการควบคุมฮาร์ดไดรฟ์เหล่านี้ด้วยอินสแตนซ์หลายตัวของตัวจัดการที่เกี่ยวข้อง พื้นผิวกราฟิกเป็นฮาร์ดแวร์ stateful ภายนอกและการเริ่มต้นสำเนาที่สองของระบบย่อยกราฟิกโดยไม่ตั้งใจนั้นเป็นอะไรที่หายนะเพราะตอนนี้ระบบย่อยกราฟิกสองระบบจะต่อสู้ซึ่งกันและกันเมื่อต้องวาดและเมื่อเขียนทับกันอย่างไม่สามารถควบคุมได้ ในทำนองเดียวกันกับระบบคิวเหตุการณ์พวกเขาจะต่อสู้กับผู้ที่ได้รับเหตุการณ์เมาส์ในแบบที่ไม่เป็นทางการ เมื่อจัดการกับฮาร์ดแวร์ภายนอกที่มีสถานะซึ่งมีเพียงหนึ่งฮาร์ดแวร์จะไม่สามารถหลีกเลี่ยงความขัดแย้งได้

อีกที่ที่ Singleton สมเหตุสมผลคือผู้จัดการ Cache แคชเป็นกรณีพิเศษ พวกเขาไม่ควรได้รับการพิจารณาว่าเป็นซิงเกิลแม้ว่าพวกเขาจะใช้เทคนิคเดียวกับซิงเกิลตันเพื่อมีชีวิตอยู่และมีชีวิตอยู่ตลอดไป บริการแคชเป็นบริการแบบโปร่งใสไม่ควรเปลี่ยนลักษณะการทำงานของโปรแกรมดังนั้นหากคุณแทนที่บริการแคชด้วยแคชแคชโปรแกรมจะทำงานได้ยกเว้นว่าจะทำงานช้ากว่าเท่านั้น เหตุผลหลักที่ผู้จัดการแคชเป็นข้อยกเว้นสำหรับ singleton แต่เนื่องจากไม่มีเหตุผลที่จะปิดบริการแคชก่อนที่จะปิดแอปพลิเคชันตัวเองเพราะมันจะทิ้งวัตถุแคชไว้ด้วยซึ่งเอาชนะจุดที่มี เดี่ยว

นี่เป็นเหตุผลที่ดีว่าทำไมบริการเหล่านี้จึงเป็นแบบซิงเกิล อย่างไรก็ตามไม่มีเหตุผลที่ดีที่จะใช้ซิงเกิลตันกับคลาสที่คุณระบุไว้

เพราะฉันจะต้องการพวกเขาในสถานที่ที่แตกต่างกันมากในเกมของฉันและการเข้าถึงที่ใช้ร่วมกันจะมีประโยชน์มาก

นั่นไม่ใช่เหตุผลสำหรับซิงเกิล นั่นคือเหตุผลสำหรับกลม นอกจากนี้ยังเป็นสัญลักษณ์ของการออกแบบที่ไม่ดีถ้าคุณต้องการผ่านสิ่งต่าง ๆ มากมายในระบบต่าง ๆ ต้องผ่านสิ่งต่าง ๆ บ่งบอกถึงการมีเพศสัมพันธ์สูงซึ่งสามารถป้องกันได้โดยการออกแบบ OO ที่ดี

เพียงแค่ดูรายการของคลาสในโพสต์ที่คุณคิดว่าจำเป็นต้องเป็นซิงเกิลฉันสามารถพูดได้ว่าครึ่งหนึ่งของพวกเขาไม่ควรเป็นซิงเกิลตันและอีกครึ่งหนึ่งดูเหมือนว่าพวกเขาไม่ควรอยู่ที่นั่นในตอนแรก การที่คุณต้องผ่านวัตถุต่าง ๆ ดูเหมือนจะเกิดจากการขาดการห่อหุ้มที่เหมาะสมมากกว่ากรณีการใช้งานที่ดีสำหรับซิงเกิล

ลองดูคลาสของคุณทีละนิด:


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

เพียงแค่จากชื่อของชั้นเรียนฉันจะบอกว่าชั้นเรียนเหล่านี้มีกลิ่นเหมือนตัวส่งสัญญาณแบบโดเมน Anemic วัตถุที่เป็นโลหิตจางมักส่งผลให้มีข้อมูลจำนวนมากที่ต้องผ่านซึ่งจะเพิ่มการมีเพศสัมพันธ์และทำให้รหัสที่เหลือนั้นยุ่งยาก นอกจากนี้คลาส anemic ยังซ่อนความจริงที่ว่าคุณอาจยังคิดว่าเป็นกระบวนการแทนที่จะใช้การวางแนววัตถุเพื่อสรุปรายละเอียด


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

ทำไมคลาสเหล่านี้จำเป็นต้องเป็นซิงเกิลตัน? ฉันคิดว่าสิ่งเหล่านี้ควรเป็นคลาสสั้น ๆ ซึ่งควรนำมาใช้เมื่อมีความจำเป็นและแยกชิ้นส่วนเมื่อผู้ใช้ทำการซื้อเสร็จหรือเมื่อไม่จำเป็นต้องแสดงโฆษณาอีกต่อไป


EntityComponentSystemManager (mananges entity creation and manipulation)

กล่าวอีกนัยหนึ่งคอนสตรัคและชั้นบริการ? ทำไมคลาสนี้ถึงไม่มีขอบเขตหรือจุดประสงค์ที่ชัดเจนแม้จะอยู่ในที่แห่งแรก


PlayerProgress (passed levels, stars)

ทำไมความคืบหน้าของผู้เล่นแยกจากคลาสผู้เล่น? คลาสผู้เล่นควรทราบวิธีการติดตามความคืบหน้าของตัวเองหากคุณต้องการใช้การติดตามความคืบหน้าในระดับที่แตกต่างจากคลาสผู้เล่นเพื่อแยกความรับผิดชอบดังนั้น PlayerProgress ควรอยู่ด้านหลังผู้เล่น


Box2dManager (manages the physics world)

ฉันไม่สามารถออกความเห็นใด ๆ เพิ่มเติมเกี่ยวกับสิ่งนี้โดยไม่ทราบว่าสิ่งที่ชั้นนี้ทำจริง แต่สิ่งหนึ่งที่ชัดเจนคือว่าชั้นนี้มีชื่อไม่ดี


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

ดูเหมือนว่าจะเป็นคลาสเดียวที่ Singleton อาจสมเหตุสมผลเนื่องจากวัตถุการเชื่อมต่อทางสังคมไม่ใช่วัตถุของคุณจริง ๆ การเชื่อมต่อทางสังคมเป็นเพียงเงาของวัตถุภายนอกที่อาศัยอยู่ในบริการระยะไกล กล่าวอีกนัยหนึ่งนี่เป็นแคชชนิดหนึ่ง เพียงให้แน่ใจว่าคุณแยกส่วนแคชอย่างชัดเจนกับส่วนบริการของบริการภายนอกเพราะส่วนหลังไม่ควรต้องเป็นซิงเกิล

วิธีหนึ่งที่คุณสามารถหลีกเลี่ยงการส่งอินสแตนซ์ของคลาสเหล่านี้โดยใช้การส่งข้อความ แทนที่จะทำการโทรโดยตรงไปยังอินสแตนซ์ของคลาสเหล่านี้แทนคุณส่งข้อความที่ส่งถึงบริการ Analytic และ SocialConnection แบบอะซิงโครนัสและบริการเหล่านี้สมัครเป็นสมาชิกเพื่อรับข้อความเหล่านี้และดำเนินการกับข้อความ เนื่องจากคิวเหตุการณ์เป็นซิงเกิลแล้วโดยการโทรซ้ำคุณจึงไม่ต้องผ่านอินสแตนซ์จริงเมื่อทำการสื่อสารกับซิงเกิล


-1

Singletons ให้ฉันไม่ดีเสมอ

ในสถาปัตยกรรมของฉันฉันสร้างคอลเลกชัน GameServices ที่ระดับเกมจัดการ ฉันสามารถค้นหาเพิ่มและลบออกจากคอลเลกชันนี้ได้ตามต้องการ

โดยการพูดอะไรบางอย่างที่อยู่ในขอบเขตทั่วโลกคุณจะพูดว่า "นี่คือพระเจ้าและไม่มีต้นแบบ" ในตัวอย่างที่คุณให้ไว้ข้างต้นฉันจะบอกว่าส่วนใหญ่เป็นผู้ช่วยชั้นหรือ GameServices ในกรณีที่เกมจะรับผิดชอบพวกเขา

แต่นั่นเป็นเพียงการออกแบบในเครื่องยนต์ของฉันสถานการณ์ของคุณอาจแตกต่างกัน

วางมันโดยตรงมากขึ้น ... ซิงเกิลตันที่แท้จริงเพียงเกมเดียวที่เป็นเจ้าของโค้ดทุกส่วน

นี่คือคำตอบนั้นขึ้นอยู่กับลักษณะของคำถามและขึ้นอยู่กับความคิดเห็นของฉันอย่างแท้จริงมันอาจไม่ถูกต้องสำหรับนักพัฒนาทั้งหมดที่อยู่ที่นั่น

แก้ไข: ตามที่ร้องขอ ...

ตกลงนี่มาจากหัวของฉันเนื่องจากฉันไม่มีรหัสของฉันอยู่ข้างหน้าฉันในขณะนี้ แต่ที่สำคัญที่สุดคือรากของเกมใด ๆ คือคลาสเกมซึ่งแท้จริงแล้วเป็นซิงเกิลตัน ... มันต้องเป็น!

ดังนั้นฉันเพิ่มต่อไปนี้ ...

class Game
{
   IList<GameService> Services;

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

ตอนนี้ฉันยังมีคลาสของระบบ (แบบคงที่) ที่มีซิงเกิลตันทั้งหมดของฉันจากโปรแกรมทั้งหมด (มีตัวเลือกเกมและการกำหนดค่าน้อยมากและนั่นเกี่ยวกับมัน) ...

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; }
}

และการใช้งาน ...

จากที่ใดก็ได้ฉันสามารถทำสิ่งที่ชอบ

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

วิธีการนี้หมายความว่าเกมของฉัน "เป็นเจ้าของ" สิ่งที่โดยทั่วไปจะถือว่าเป็น "ซิงเกิล" และฉันสามารถขยายคลาส GameService พื้นฐานได้ตามที่ฉันต้องการ ฉันมีอินเทอร์เฟซอย่าง IUpdateable ที่เกมของฉันตรวจสอบและโทรเข้าใช้บริการใด ๆ ที่ใช้งานได้ตามต้องการสำหรับสถานการณ์เฉพาะ

ฉันยังมีบริการที่สืบทอด / ขยายบริการอื่น ๆ สำหรับสถานการณ์ที่เฉพาะเจาะจงมากขึ้น

ตัวอย่างเช่น ...

ฉันมีบริการฟิสิกส์ที่จัดการกับการจำลองทางฟิสิกส์ของฉัน แต่ขึ้นอยู่กับการจำลองทางฟิสิกส์ของฉากอาจต้องใช้พฤติกรรมที่แตกต่างกันเล็กน้อย


1
ไม่ควรให้บริการเกมเป็นตัวอย่างเพียงครั้งเดียวใช่หรือไม่ ถ้าเป็นเช่นนั้นมันเป็นเพียงรูปแบบซิงเกิลที่ไม่บังคับใช้ในระดับชั้นเรียนซึ่งเลวร้ายยิ่งกว่าซิงเกิลที่ถูกนำไปใช้อย่างถูกต้อง
GameAlchemist

2
@Wardy ฉันไม่เข้าใจสิ่งที่คุณทำอย่างชัดเจน แต่ดูเหมือนว่า Singleton-Facade ที่ฉันได้อธิบายไว้และมันควรจะเป็นวัตถุของพระเจ้าใช่ไหม?
Narek

10
มันเป็นเพียงรูปแบบซิงเกิลที่นำมาใช้ในระดับเกม ดังนั้นสิ่งที่น่าสนใจที่สุดคือการอนุญาตให้คุณแกล้งคุณไม่ได้ใช้ Singleton มีประโยชน์ถ้าเจ้านายของคุณมาจากคริสตจักรต่อต้านรูปแบบ
GameAlchemist

2
ฉันไม่แน่ใจว่าสิ่งนี้แตกต่างจากการใช้ซิงเกิลตันอย่างไร ไม่ใช่ความแตกต่างเพียงอย่างเดียวในการที่คุณเข้าถึงบริการที่มีอยู่ประเภทนี้หรือไม่? คือvar tService = Sys.Game.getService<T>(); tService.Something();แทนที่จะข้ามgetServiceส่วนและเข้าถึงโดยตรงหรือไม่
Christian

1
@ คริสตชน: อันที่จริงนั่นคือสิ่งที่ปฏิปักษ์ของบริการค้นหา เห็นได้ชัดว่าชุมชนนี้ยังคงคิดว่าเป็นรูปแบบการออกแบบที่แนะนำ
Magus

-1

ส่วนประกอบที่สำคัญของการกำจัดซิงเกิลซึ่งฝ่ายตรงข้ามของซิงเกิลมักจะล้มเหลวในการพิจารณาคือการเพิ่มตรรกะพิเศษเพื่อจัดการกับสถานะที่ซับซ้อนมากขึ้นซึ่งวัตถุนั้นแค็ปซูเลชั่นเมื่อซิงเกิลให้ค่าอินสแตนซ์ อันที่จริงแม้การกำหนดสถานะของวัตถุหลังจากเปลี่ยนซิงเกิลตันด้วยตัวแปรอินสแตนซ์อาจเป็นเรื่องยาก แต่โปรแกรมเมอร์ที่เปลี่ยนซิงเกิลตันด้วยตัวแปรอินสแตนซ์ควรเตรียมที่จะจัดการกับมัน

เปรียบเทียบตัวอย่างเช่นของ Java HashMapกับ Dictionary.NET ทั้งสองมีความคล้ายคลึงกัน แต่มีความแตกต่างที่สำคัญ: Java HashMapเป็นรหัสยากที่จะใช้equalsและhashCode(มันมีประสิทธิภาพใช้ตัวเปรียบเทียบเดี่ยว) ในขณะที่. NET Dictionaryสามารถยอมรับตัวอย่างของIEqualityComparer<T>พารามิเตอร์คอนสตรัคเตอร์ ค่าใช้จ่ายในการรักษาIEqualityComparerน้อยที่สุด แต่ความซับซ้อนที่แนะนำนั้นไม่ใช่

เนื่องจากแต่ละDictionaryอินสแตนซ์ห่อหุ้ม an IEqualityComparer, สถานะของมันไม่ได้เป็นเพียงการแม็พระหว่างคีย์ที่มีอยู่จริงและค่าที่เกี่ยวข้อง แต่แทนการแม็พระหว่างชุดการเทียบเคียงที่กำหนดโดยคีย์และตัวเปรียบเทียบและค่าที่เกี่ยวข้อง หากทั้งสองกรณีd1และd2การDictionaryระงับการอ้างอิงถึงเดียวกันIEqualityComparerก็จะเป็นไปได้ในการผลิตตัวอย่างใหม่d3ดังกล่าวว่าสำหรับการใด ๆx, จะเท่ากับd3.ContainsKey(x) d1.ContainsKey(x) || d2.ContainsKey(x)อย่างไรก็ตามหากd1และd2อาจมีการอ้างอิงถึงการเปรียบเทียบความเท่าเทียมกันโดยพลการที่แตกต่างกันโดยทั่วไปจะไม่มีทางที่จะผสานเข้าด้วยกันเพื่อให้มั่นใจว่าเงื่อนไขนั้นคงอยู่

การเพิ่มตัวแปรอินสแตนซ์ในความพยายามที่จะกำจัดซิงเกิลตัน แต่การไม่สามารถอธิบายสถานะใหม่ที่เป็นไปได้ที่อาจถูกบอกเป็นนัยโดยตัวแปรอินสแตนซ์เหล่านั้นสามารถสร้างโค้ดที่จบลงด้วยการเปราะมากกว่าที่จะเป็นแบบซิงเกิล หากทุกอินสแตนซ์ของวัตถุมีเขตข้อมูลแยกต่างหาก แต่สิ่งต่าง ๆ จะทำงานผิดพลาดอย่างรุนแรงเว้นแต่ว่าพวกเขาจะระบุอินสแตนซ์ของวัตถุเดียวกันทั้งหมดจากนั้นเขตข้อมูลใหม่ทั้งหมดที่ซื้อมามีจำนวนเพิ่มขึ้น


1
แม้ว่านี่จะเป็นแง่มุมที่น่าสนใจสำหรับการอภิปราย แต่ฉันไม่คิดว่ามันจะตอบคำถามที่ OP ถามจริง ๆ
Josh

1
@JoshPetrie: ความคิดเห็นในโพสต์ดั้งเดิม (เช่นโดย Magus) แนะนำว่าค่าใช้จ่ายในการดำเนินการอ้างอิงเพื่อหลีกเลี่ยงการใช้ซิงเกิลนั้นไม่ดี ดังนั้นฉันจะพิจารณาค่าใช้จ่ายทางความหมายที่เกี่ยวข้องกับการมีวัตถุทุกอย่างห่อหุ้มการอ้างอิงเพื่อจุดประสงค์ในการหลีกเลี่ยงซิงเกิล หนึ่งควรหลีกเลี่ยงซิงเกิลตันในกรณีที่สามารถหลีกเลี่ยงได้อย่างง่ายและราคาถูก แต่โค้ดที่ไม่ต้องการซิงเกิลซิงเกิล แต่อาจจะแตกได้ทันทีที่มีอินสแตนซ์ของสิ่งที่มีอยู่หลายตัวแย่กว่าโค้ดที่ใช้ซิงเกิล
supercat

2
รู้รอบไม่ได้มีไว้สำหรับความคิดเห็นที่พวกเขามีไว้สำหรับตอบคำถามโดยตรง
ClassicThunder

@ClassicThunder: คุณชอบการแก้ไขที่ดีขึ้นหรือไม่
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.