เมื่อมีความเหมาะสมโสด [ปิด]


65

บางคนคิดว่ารูปแบบซิงเกิลนั้นเป็นรูปแบบต่อต้านเสมอ คุณคิดอย่างไร?


ตอนนี้ฉันต้องการลิงค์นี้สำหรับการสนทนาทั้งหมดที่เกี่ยวกับ Singletons
cregox

คำตอบ:


32

การวิพากษ์วิจารณ์หลักสองข้อของซิงเกิลตกลงไปในสองค่ายจากสิ่งที่ฉันสังเกตเห็น:

  1. Singletons ถูกใช้ในทางที่ผิดและถูกทารุณกรรมโดยโปรแกรมเมอร์ที่มีความสามารถน้อยดังนั้นทุกอย่างจึงกลายเป็นซิงเกิลและคุณเห็นโค้ดที่เกลื่อนไปด้วย Class :: get_instance () การอ้างอิง โดยทั่วไปการพูดมีเพียงหนึ่งหรือสองทรัพยากร (เช่นการเชื่อมต่อฐานข้อมูลเป็นต้น) ที่มีคุณสมบัติเหมาะสมสำหรับการใช้รูปแบบ Singleton
  2. ซิงเกิลตันเป็นคลาสสแตติกที่ต้องพึ่งพาเมธอดและคุณสมบัติแบบสแตติกหนึ่งรายการหรือมากกว่า ทุกสิ่งคงที่แสดงปัญหาที่แท้จริงและจับต้องได้เมื่อคุณพยายามทำการทดสอบหน่วยเพราะสิ่งเหล่านี้เป็นตัวแทนของจุดจบในรหัสของคุณซึ่งไม่สามารถเยาะเย้ยหรือทำไม่ได้ เป็นผลให้เมื่อคุณทดสอบคลาสที่อาศัย Singleton (หรือวิธีการคงที่หรือคลาสอื่น ๆ ) คุณไม่เพียงทดสอบคลาสนั้น แต่ยังรวมถึงวิธีหรือคลาสแบบสแตติก

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


6
ฉันแค่อยากจะบอกว่าคุณสามารถเยาะเย้ยวิธีการแบบคงที่ใน Java กับ JMockit ( code.google.com/p/jmockit ) มันเป็นเครื่องมือที่มีประโยชน์มาก
Michael K

3
ความคิดบางอย่าง: คอนเทนเนอร์เป็นซิงเกิลดังนั้นคุณจึงไม่ได้กำจัดซิงเกิล นอกจากนี้หากคุณเขียนไลบรารีด้วย API สาธารณะคุณจะไม่สามารถบังคับให้ผู้ใช้ API ใช้คอนเทนเนอร์ของคุณได้ หากคุณต้องการผู้จัดการทรัพยากรระดับโลกในห้องสมุดของคุณ (เช่นการป้องกันการเข้าถึงทรัพยากรทางกายภาพเดียวเป็นต้น) จากนั้นคุณต้องใช้ Singleton
Scott Whitlock

2
@Scott Whitlock ทำไมถึงต้องเป็นซิงเกิล? เพียงเพราะมีเพียงอินสแตนซ์เดียวเท่านั้นที่ไม่สามารถทำได้ ห้องสมุดใด ๆ IoC จะจัดการชนิดของการพึ่งพานี้ ...
MattDavey

วิธีแก้ปัญหานี้เสนอให้รู้จักกันในชื่อToolbox
cregox

41

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

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


หวังว่าฉันจะสามารถโหวตมากกว่าหนึ่งครั้ง!
MattDavey

34

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

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


3
ดังนั้นในคำอื่น ๆ คุณจะต่อต้านการใช้ฐานข้อมูลในโปรแกรมใด ๆ
Kendall Helmstetter Gelner

มีวิดีโอพูดคุยเกี่ยวกับเทคโนโลยีของ Google ที่โด่งดังที่สะท้อนความเห็นของคุณ youtube.com/watch?v=-FRm3VPhseI
Martin York

2
@KendallHelmstetterGelner: มีความแตกต่างระหว่างสถานะและที่เก็บข้อมูลรอง ไม่น่าเป็นไปได้ที่คุณสามารถเก็บ DB ทั้งหมดไว้ในหน่วยความจำได้
Martin York

7

ทำไมคนใช้มัน?

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

  1. โสดอินสแตนซ์

    "ใช้คลาส C อินสแตนซ์เดียวทั่วทั้งแอปพลิเคชัน"

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

  2. โสดinstantiation

    "ไม่อนุญาตให้คลาส C ถูกสร้างอินสแตนซ์มากกว่าหนึ่งครั้ง (ต่อกระบวนการ, ต่อคำขอ, ฯลฯ )"

    สิ่งนี้มีความเกี่ยวข้องเฉพาะเมื่ออินสแตนซ์ชั้นเรียนนั้นมีผลข้างเคียงที่ขัดแย้งกับอินสแตนซ์อื่น

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

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

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

  3. การเข้าถึงระดับโลก

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

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

  4. ขี้เกียจ instantiation

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

การใช้งานทั่วไป

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

นอกเหนือจากปัญหาที่กล่าวมาข้างต้นกัดด้วยความรับผิดชอบหลักเดียวเพราะชั้นเรียนจะควบคุมการสร้างอินสแตนซ์และวงจรชีวิตของตัวเองนอกเหนือจากความรับผิดชอบอื่น ๆ ที่ชั้นเรียนมีอยู่แล้ว

ข้อสรุป

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

อย่างไรก็ตามมีกรณีการใช้งานที่คุณมีข้อกำหนดที่ถูกต้องต่อไปนี้เหลืออยู่:

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

ดังนั้นนี่คือสิ่งที่คุณสามารถทำได้ในกรณีนี้:

  • สร้างคลาส C ที่คุณต้องการสร้างอินสแตนซ์ด้วยตัวสร้างสาธารณะ

  • สร้างคลาส S ที่แยกต่างหากพร้อมกับตัวแปรอินสแตนซ์แบบคงที่และวิธีการแบบสแตติก S :: getInstance () พร้อมด้วยการสร้างอินสแตนซ์ขี้เกียจที่จะใช้คลาส C สำหรับอินสแตนซ์

  • กำจัดผลข้างเคียงทั้งหมดออกจากตัวสร้างของ C แทนให้ใส่ผลข้างเคียงเหล่านี้ในวิธี S :: getInstance ()

  • หากคุณมีคลาสมากกว่าหนึ่งคลาสที่มีข้อกำหนดด้านบนคุณอาจพิจารณาจัดการอินสแตนซ์ของคลาสด้วยคอนเทนเนอร์เซอร์วิสท้องถิ่นขนาดเล็กและใช้อินสแตนซ์แบบคงที่สำหรับคอนเทนเนอร์เท่านั้น ดังนั้น S :: getContainer () จะให้บริการคอนเทนเนอร์ instantiated ขี้เกียจคุณและคุณได้รับวัตถุอื่น ๆ จากภาชนะ

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

  • สร้างอินเตอร์เฟสที่คลาส C เป็นทางเลือกและใช้สิ่งนี้เพื่อบันทึกค่าส่งคืนของ S :: getInstance ()

(เรายังเรียกสิ่งนี้ว่าซิงเกิลหรือไม่? ฉันจะทิ้งไว้ที่ส่วนความคิดเห็น .. )

ประโยชน์ที่ได้รับ:

  • คุณสามารถสร้างอินสแตนซ์แยกต่างหากของ C สำหรับการทดสอบหน่วยโดยไม่ต้องสัมผัสสถานะโกลบอลใด ๆ

  • การจัดการอินสแตนซ์ถูกแยกออกจากคลาส -> การแยกข้อกังวลหลักการความรับผิดชอบเดี่ยว

  • มันจะค่อนข้างง่ายที่จะให้ S :: getInstance () ใช้คลาสอื่นสำหรับอินสแตนซ์หรือแม้แต่กำหนดคลาสที่จะใช้


1

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

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

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

สำหรับเมื่อไม่ใช้ให้ดูที่ 3 ด้านบนและเปลี่ยนเป็นตรงกันข้าม


3
-0 แต่ฉันเกือบ -1 ฉันอยากจะส่งมันทุกที่ในรหัสแทนที่จะใช้ซิงเกิลตัน แต่ถ้าฉันขี้เกียจจริงๆฉันอาจทำเช่นนั้น ฉันจะใช้ vars ทั่วโลกแทนถ้าฉันสามารถหรือคงสมาชิก vars ทั่วโลกเป็นที่นิยมมาก (สำหรับฉัน) จากนั้นซิงเกิล Globals ไม่โกหก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.