ทางเลือกในการ Singletons / globals


16

ฉันเคยได้ยินครั้งนับไม่ถ้วนเกี่ยวกับข้อผิดพลาดของ Singletons / กลมและฉันเข้าใจว่าทำไมพวกเขาถึงขมวดคิ้วบ่อยครั้ง

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

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

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

นี่เป็นกรณีที่ Singleton น่าจะเป็นสิ่งที่ดีหรือมีวิธีแก้ปัญหาอันงดงามที่ฉันขาดไปหรือไม่?

คำตอบ:


16

อย่าคุยเดี่ยวและกลม ในขณะที่ตัวแปรทั่วโลกบางประเภทนั้นจำเป็นโดยทั่วไปแล้วซิงเกิลตันไม่ได้เป็นเพียงการแทนที่ตัวแปรทั่วโลก แต่เป็นวิธีหลัก ในการแก้ปัญหาของลำดับการเริ่มต้นแบบคงที่ใน C ++ ( และ FQA ) (ในภาษาอื่น ๆ มันเป็นวิธีการแก้ไขข้อบกพร่องทางภาษาที่แตกต่างกันเช่นการขาดตัวแปรทั่วโลกและฟังก์ชั่นเปลือย)

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

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


+1 คำตอบที่ดีและขอบคุณสำหรับลิงค์รูปแบบของตัวระบุตำแหน่งบริการ นั่นเป็นการอ่านที่น่าสนใจมาก
bummzack

1

หากคุณไม่ได้ / ไม่สามารถมีส่วนหนึ่งของรหัส "รู้" เกี่ยวกับข้อมูลบางส่วนได้อย่างน่าอัศจรรย์ก็จะต้องมีการส่งผ่านอย่างใด อย่างไรก็ตามนั่นไม่ได้หมายความว่าจะต้องผ่านการผ่านการโต้แย้งเท่านั้น

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

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


1

ฉันคิดว่า Jason D ถูกต้องจริง ๆ - นี่คือวิธีที่ฉันจัดการ:

เกมมีอินสแตนซ์ของ AssetManager ซึ่งเป็นวัตถุที่คุณสามารถรับสินทรัพย์ได้ตามชื่อ

ในเกมส์:

assetManager = new AssetManager();
screenManager = new ScreenManager();
screenManager.assetManager = assetManager;

ใน ScreenManager:

screen = new Screen();
screen.assetManager = assetManager;

ในหน้าจอ:

myAsset = assetManager.getBitmp("lava.png");

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


0

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

มันให้ความยืดหยุ่นทั้งหมดของซิงเกิลตันโดยไม่มีปัญหาใกล้เคียง


ความเป็นไปได้ค่อนข้าง จำกัด คือการทำให้คลาสคงที่ (ซึ่งฉันไม่คิดว่าเป็นไปได้สำหรับ AssetManager และเป็นไปได้เฉพาะในภาษาที่มีคลาสคงที่เลย) แต่มันใช้ได้ผลก็ต่อเมื่อคุณไม่ต้องการรับมรดก / ความหลากหลาย มันเป็นทางออกที่ยืดหยุ่นมาก:

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

นี่เป็นวิธีการแบบสแตติก แต่สามารถนำไปใช้กับคลาสแบบคงที่ได้เช่นกัน


คุณหมายถึงอะไรโดย "ทำให้คลาสคงที่"? C ++ ไม่มีคลาสแบบสแตติก คุณหมายถึงมีวิธีการคงที่เท่านั้น? ทำไมต้องมีคลาสแทนเนมสเปซ?

1
@ โจ: คำถามไม่ได้มุ่งเน้นไปที่ C ++ ตามที่ฉันเข้าใจ ใน C # หรือ Java คุณสามารถสร้างคลาสสแตติกและฉันหมายถึงคลาสเหล่านั้น นอกจากนี้อย่างที่ฉันพูดคลาสสแตติกไม่ใช่วิธีที่ดีที่สุดส่วนใหญ่ แต่ในบางกรณีมันอาจทำงานได้เหมือนโลก
Michael Klement
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.