ดังนั้นซิงเกิลตันก็แย่แล้วอะไรนะ?


553

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

นี่คือตัวอย่างจริงจากโครงการล่าสุดที่สำคัญที่ฉันเข้าร่วม:

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

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


10
ปัญหาของการใช้ซิงเกิลตันที่ควรแก้ไขคืออะไร การแก้ปัญหานั้นดีกว่าทางเลือกอื่น (เช่นคลาสคงที่) ดีกว่าได้อย่างไร?
อานนท์

14
@Anon: การใช้คลาสคงที่ทำให้สถานการณ์ดีขึ้นได้อย่างไร ยังคงมีเพศสัมพันธ์อย่างแน่นหนา?
Martin York

5
@ มาร์ติน: ฉันไม่แนะนำให้มันทำให้ "ดีกว่า" ฉันแนะนำว่าในกรณีส่วนใหญ่ซิงเกิลตันเป็นทางออกในการค้นหาปัญหา
อานนท์

9
@Anon: ไม่จริง คลาสแบบสแตติกให้คุณ (เกือบ) ไม่มีการควบคุมการสร้างอินสแตนซ์และทำให้หลายเธรดทำได้ยากยิ่งขึ้นกว่าแบบซิงเกิล Singletons ยังสามารถนำอินเตอร์เฟสไปใช้อย่างน้อยคลาสแบบสแตติกที่ไม่สามารถทำได้ คลาสสแตติกจะมีข้อได้เปรียบอย่างแน่นอน แต่ในกรณีนี้ซิงเกิลนั้นมีความชั่วร้ายน้อยกว่าสองอย่าง ระดับคงที่ดำเนินการใด ๆของรัฐที่ไม่แน่นอนใด ๆ เป็นเหมือนนีออนกระพริบใหญ่ "คำเตือน: การออกแบบ BAD ล่วงหน้า" สัญญาณ
Aaronaught

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

คำตอบ:


809

มันเป็นสิ่งสำคัญที่จะแยกแยะความแตกต่างระหว่างที่นี่กรณีเดียวและรูปแบบการออกแบบโทน

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

รูปแบบการออกแบบ Singleton เป็นประเภทของอินสแตนซ์เดี่ยวที่เฉพาะเจาะจงอย่างยิ่ง

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

เป็นเพราะตัวเลือกการออกแบบที่เฉพาะเจาะจงนี้ซึ่งรูปแบบดังกล่าวนำเสนอปัญหาระยะยาวหลายประการที่อาจเกิดขึ้น:

  • ไม่สามารถใช้คลาส abstract หรืออินเตอร์เฟส
  • ไม่สามารถคลาสย่อย;
  • การมีเพศสัมพันธ์สูงทั่วทั้งแอปพลิเคชัน (ยากที่จะแก้ไข);
  • ทดสอบยาก (ไม่สามารถปลอม / จำลองในการทดสอบหน่วย)
  • ยากต่อการขนานในกรณีที่สถานะไม่แน่นอน (ต้องล็อคอย่างกว้างขวาง);
  • และอื่น ๆ

ไม่มีอาการเหล่านี้ที่เกิดเฉพาะถิ่นกับอินสแตนซ์เดี่ยวเพียงรูปแบบซิงเกิล

คุณสามารถทำอะไรแทน อย่าใช้รูปแบบซิงเกิล

อ้างจากคำถาม:

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

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

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

สิ่งแรกที่ฉันจะทำคือแยกส่วนการทำงานต่างของแคชออกเป็นส่วนต่อประสานที่แยกจากกัน ตัวอย่างเช่นสมมติว่าคุณกำลังทำตัว YouTube ที่แย่ที่สุดในโลกโดยอ้างอิงจาก Microsoft Access:

                          MSAccessCache
                                ▲
                                |
              + + ----------------- ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostP PopularPage

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

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

ทำไมคุณควรใช้การออกแบบตามอินเตอร์เฟส สามเหตุผล:

  1. ทำให้โค้ดอ่านง่ายขึ้น คุณสามารถเข้าใจได้อย่างชัดเจนจากอินเทอร์เฟซว่าข้อมูลใดขึ้นอยู่กับคลาสที่พึ่งพา

  2. ถ้าและเมื่อคุณทราบว่า Microsoft Access ไม่ใช่ตัวเลือกที่ดีที่สุดสำหรับแบ็คเอนด์ข้อมูลคุณสามารถแทนที่มันด้วยสิ่งที่ดีกว่า - สมมุติว่า SQL Server

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

หากคุณต้องการที่จะก้าวไปอีกขั้นหนึ่งคุณสามารถใช้คอนเทนเนอร์ IoC (กรอบงาน DI) เช่น Spring (Java) หรือ Unity (.NET) เกือบทุกเฟรมเวิร์ก DI จะทำการจัดการอายุการใช้งานของตนเองและอนุญาตให้คุณกำหนดบริการเฉพาะเป็นอินสแตนซ์เดียว (มักเรียกว่า "singleton" แต่นั่นเป็นเพียงเพื่อความคุ้นเคย) โดยทั่วไปเฟรมเวิร์กเหล่านี้จะช่วยให้คุณประหยัดงานลิงส่วนใหญ่ที่ผ่านไปด้วยตนเอง แต่มันไม่จำเป็นอย่างยิ่ง คุณไม่จำเป็นต้องมีเครื่องมือพิเศษใด ๆ เพื่อใช้การออกแบบนี้

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

                                                        + - IMediaRepository
                                                        |
                          แคช (ทั่วไป) --------------- + - IProfileRepository
                                ▲ |
                                | + - IPageRepository
              + + ----------------- ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostP PopularPage

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

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

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


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

4
ทำไมแคชไม่สามารถเป็นซิงเกิลตันได้? ไม่ใช่ซิงเกิลถ้าคุณผ่านมันไปและใช้การฉีดแบบพึ่งพาได้หรือไม่? ซิงเกิลตันกำลัง จำกัด ตัวเราให้อยู่ในอินสแตนซ์เดียวไม่ใช่เกี่ยวกับการเข้าถึงได้อย่างไร? ดูสิ่งที่ฉันทำในวันนี้: assoc.tumblr.com/post/51302471844/the-misunderstood-singleton
Erik Engheim

29
@AdamSmith: คุณอ่านคำตอบนี้จริงหรือไม่? คำถามของคุณได้รับคำตอบในสองย่อหน้าแรก รูปแบบซิงเกิล! == อินสแตนซ์เดี่ยว
Aaronaught

5
@Cawas และ Adam Smith - อ่านลิงก์ของคุณฉันรู้สึกว่าคุณไม่ได้อ่านคำตอบนี้จริงๆ - ทุกอย่างอยู่ในนั้นแล้ว
Wilbert

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

48

ในกรณีที่คุณให้ดูเหมือนว่าการใช้ซิงเกิลตันไม่ใช่ปัญหา แต่เป็นอาการของปัญหา - ปัญหาด้านสถาปัตยกรรมที่ใหญ่กว่า

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

ปัญหามีแนวโน้มว่าการพึ่งพาระหว่างส่วนต่าง ๆ ของระบบไม่ได้ตั้งค่าอย่างถูกต้องและอาจเป็นไปได้ว่าเป็นระบบ

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

อีกครั้งเรากำลังดูปัญหาสถาปัตยกรรมและการออกแบบขนาดใหญ่

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

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

แต่ปัญหาของ Singleton (อย่างน้อยการใช้งานทั่วไปของ Singleton เป็น class / property แบบสแตติก) คือคลาสอื่น ๆ ที่ใช้มันในการค้นหามัน

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

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


2
มีข้อมูลจำนวนมหาศาลที่หน้าจอบางหน้าอาจต้องการ แต่ไม่จำเป็นต้องใช้ และคุณไม่รู้จนกระทั่งมีการกระทำของผู้ใช้ที่กำหนดสิ่งนี้ - และมีหลายอย่างรวมกันมากมาย ดังนั้นวิธีที่ทำคือการมีข้อมูลทั่วโลกทั่วไปที่เก็บไว้ในแคชและซิงค์ในไคลเอนต์ (ส่วนใหญ่ได้จากการเข้าสู่ระบบ) และจากนั้นการร้องขอที่ตามมาสร้างแคชมากขึ้นเนื่องจากข้อมูลที่ร้องขออย่างชัดเจน เซสชั่นเดียวกัน โฟกัสอยู่ที่การลดการร้องขอไปยังเซิร์ฟเวอร์ดังนั้นจึงจำเป็นต้องใช้แคชฝั่งไคลเอ็นต์ <cont>
Bobby Tables

1
<cont> มันโปร่งใสเป็นหลัก ในแง่ที่ว่ามีการเรียกกลับจากเซิร์ฟเวอร์หากข้อมูลที่จำเป็นบางอย่างยังไม่ถูกแคช แต่การนำไปปฏิบัติ (ตามหลักเหตุผลและทางกายภาพ) ของตัวจัดการแคชนั้นคือ Singleton
Bobby Tables

6
ฉันอยู่กับ qstarin ที่นี่: วัตถุที่เข้าถึงข้อมูลไม่ควรรู้ (หรือจำเป็นต้องรู้) ว่าข้อมูลถูกแคช (นั่นคือรายละเอียดการนำไปใช้) ผู้ใช้ข้อมูลเพียงแค่ขอข้อมูล (หรือขอให้ส่วนต่อประสานเพื่อดึงข้อมูล)
Martin York

1
การแคชคือรายละเอียดของการใช้งาน มีอินเทอร์เฟซสำหรับสอบถามข้อมูลและวัตถุที่รับไม่ทราบว่ามาจากแคชหรือไม่ แต่ภายใต้ตัวจัดการแคชนี้คือ Singleton
Bobby Tables

2
@Bobby Tables: สถานการณ์ของคุณไม่น่ากลัวอย่างที่คิด Singleton นั้น (สมมติว่าคุณหมายถึงคลาสแบบสแตติกไม่ใช่แค่วัตถุที่มีอินสแตนซ์ที่มีชีวิตตราบใดที่แอพ) ยังคงเป็นปัญหาอยู่ กำลังซ่อนความจริงที่ว่าวัตถุที่ให้ข้อมูลของคุณมีการพึ่งพาผู้ให้บริการแคช มันจะดีกว่าถ้ามันชัดเจนและทำให้เป็นภายนอก แยกพวกมันออก มันเป็นสิ่งสำคัญสำหรับความสามารถในการทดสอบที่คุณสามารถทดแทนส่วนประกอบได้อย่างง่ายดายและผู้ให้บริการแคชเป็นตัวอย่างที่สำคัญของส่วนประกอบดังกล่าว
quentin-starin

45

ฉันเขียนบททั้งหมดเพื่อแค่คำถามนี้ ส่วนใหญ่ในบริบทของเกม แต่ส่วนใหญ่ควรใช้นอกเกม

TL; DR:

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

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

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

  • คลองมันทั้งหมด ฉันเห็นคลาสแบบซิงเกิลจำนวนมากที่ไม่มีสถานะใด ๆ และเป็นเพียงฟังก์ชั่นตัวช่วย สิ่งเหล่านั้นไม่ต้องการอินสแตนซ์เลย เพียงแค่ทำให้พวกเขาฟังก์ชั่นคงที่หรือย้ายพวกเขาเป็นหนึ่งในชั้นเรียนที่ฟังก์ชั่นใช้เป็นอาร์กิวเมนต์ คุณจะไม่จำเป็นต้องพิเศษระดับถ้าคุณเพียงแค่จะทำMath123.Abs()

  • ผ่านมันไป วิธีแก้ปัญหาอย่างง่าย ๆ หากวิธีการต้องการวัตถุอื่น ๆ คือการผ่านมันไปไม่มีอะไรผิดปกติกับการส่งวัตถุบางอย่างไปรอบ ๆ

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


1
คุณสรุปที่นี่ได้ไหม
Nicole

1
เสร็จสิ้น แต่ฉันยังคงสนับสนุนให้คุณอ่านทั้งบท
เอื้อเฟื้อเผื่อแผ่

4
ทางเลือกอื่น: การพึ่งพาการฉีด!
Brad Cupit

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

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

21

ไม่ใช่รัฐทั่วโลกต่อ se ที่เป็นปัญหา

global mutable stateจริงๆคุณจะต้องเป็นห่วงเกี่ยวกับ สถานะคงที่ไม่ได้รับผลกระทบจากผลข้างเคียงจึงมีปัญหาน้อย

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

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

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

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

นั่นทำให้รู้สึก นี่ยังทำให้ฉันคิดว่าฉันไม่เคยทำร้าย Singletons จริงๆ แต่เพิ่งเริ่มสงสัยการใช้งานใด ๆ ของพวกเขา แต่ไม่สามารถคิดถึงการใช้งานที่ไม่เหมาะสมใด ๆ ที่ฉันได้ทำไปตามจุดเหล่านี้ :)
Bobby Tables

2
(คุณอาจหมายถึงต่อ seไม่ "ต่อพูด")
nohat

4
@nohat: ฉันเป็นเจ้าของภาษาของ "Queens English" และปฏิเสธสิ่งที่ดูเป็นภาษาฝรั่งเศสเว้นแต่ว่าเราจะทำให้ดีขึ้น (เช่นle weekendโอ๊ะโอที่เป็นหนึ่งในของเรา) ขอบคุณ :-)
Martin York

21
ต่อ seคือละติน
อานนท์

2
@Anon: ตกลง นั่นไม่เลวร้ายนัก ;-)
มาร์ตินยอร์

19

แล้วไง? เนื่องจากไม่มีใครกล่าวว่า: กล่องเครื่องมือ นั่นคือถ้าคุณต้องการตัวแปรทั่วโลก

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

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

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

}

แต่คาดเดาอะไร มันเป็นซิงเกิล!

และซิงเกิลคืออะไร?

บางทีนั่นอาจเป็นสิ่งที่ทำให้เกิดความสับสน

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

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

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

อย่าเข้าใจฉันผิด!

เพียงแค่ใส่เช่นเดียวกับvars โลก , singletons ควรยังคงหลีกเลี่ยงได้ตลอดเวลา พิเศษเพราะพวกเขาถูกทารุณกรรมมากเกินไป แต่ไม่สามารถหลีกเลี่ยง vars ทั่วโลกได้และเราควรใช้มันในกรณีสุดท้าย

อย่างไรก็ตามมีคำแนะนำอื่น ๆ อีกมากมายนอกเหนือจากกล่องเครื่องมือและเช่นเดียวกับกล่องเครื่องมือแต่ละอันมีแอปพลิเคชัน ...

ทางเลือกอื่น ๆ

  • บทความที่ดีที่สุดที่ฉันได้อ่านเพียงเกี่ยวกับ singletonsแสดงให้เห็นบริการสเป็นทางเลือก สำหรับฉันนั้นเป็นเพียง " กล่องเครื่องมือแบบคงที่ " ถ้าคุณต้องการ พูดอีกอย่างหนึ่งคือทำให้ Service Locator เป็น Singleton และคุณมี Toolbox นั่นไม่ขัดกับคำแนะนำเริ่มต้นในการหลีกเลี่ยงซิงเกิลแน่นอน แต่นั่นเพียงเพื่อบังคับใช้ปัญหาของซิงเกิลคือการใช้งานไม่ใช่รูปแบบในตัวเอง

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

ทั้งสองทางเลือกข้างต้นเป็นทางเลือกที่ดี แต่ทุกอย่างขึ้นอยู่กับการใช้งานของคุณ

ตอนนี้ควรหลีกเลี่ยงซิงเกิลตันที่มีค่าใช้จ่ายทั้งหมดเป็นเพียงความผิด ...

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

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

ตัวอย่างการปฏิบัติ

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

เข้าสู่ระบบ, บางคนจะเถียงเป็นตัวอย่างที่สมบูรณ์แบบเดี่ยวเพราะและฉันพูด:

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

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

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


@gnat ขอบคุณ! ฉันแค่คิดที่จะแก้ไขคำตอบเพื่อเพิ่มคำเตือนบางอย่างเกี่ยวกับการใช้ซิงเกิล ... คำพูดของคุณลงตัวอย่างสมบูรณ์แบบ!
cregox

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

@gnat ใช่ฉันรู้ว่านี่เป็นการต่อสู้ที่ยาวนาน ฉันหวังว่าเวลาจะบอก ;-)
cregox

1
ฉันเห็นด้วยกับทิศทางทั่วไปของคุณที่นี่ถึงแม้ว่ามันอาจจะเป็นเรื่องที่กระตือรือร้นเล็กน้อยเกี่ยวกับห้องสมุดที่ดูเหมือนจะไม่มากไปกว่าภาชนะ IoC ที่แสนเปลือยเปล่า (เช่นพื้นฐานมากกว่าเช่นFunq ) ตัวระบุบริการเป็นจริงรูปแบบการต่อต้าน เป็นเครื่องมือที่มีประโยชน์ส่วนใหญ่ในโครงการมรดก / บราวน์ฟิลด์พร้อมกับ "คนจนคนจน" ซึ่งมันแพงเกินไปที่จะปรับโครงสร้างทุกอย่างเพื่อใช้คอนเทนเนอร์ IoC อย่างถูกต้อง
Aaronaught

1
@Cawas: อ่าขอโทษ - java.awt.Toolkitได้สับสนกับ จุดของฉันเหมือนกัน: Toolboxฟังเหมือนคว้ากระเป๋าบิตและชิ้นส่วนที่ไม่เกี่ยวข้องมากกว่าชั้นที่เชื่อมโยงกันที่มีจุดประสงค์เดียว ไม่ชอบการออกแบบที่ดีสำหรับฉัน (โปรดสังเกตว่าบทความที่คุณอ้างถึงจากปี 2001 ก่อนที่จะฉีดพึ่งพาและภาชนะบรรจุ DI ได้กลายเป็นเรื่องธรรมดา.)
จอน Skeet

5

ปัญหาหลักของฉันกับรูปแบบการออกแบบเดี่ยวคือมันยากมากที่จะเขียนการทดสอบหน่วยที่ดีสำหรับการใช้งานของคุณ

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

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

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

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


อธิบายอย่างนี้เป็นคำตอบที่อธิบายปัญหาเกี่ยวกับการทดสอบ Singletons ได้ดีที่สุด
JoséTomás Tocino

4

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

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

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

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

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


3

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

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


2

Singletons เป็นเพียงการฉายภาพของสถาปัตยกรรมเชิงบริการในโปรแกรม

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

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

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

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

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

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

สิ่งที่ต้องการ:

//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

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

สิ่งที่คุณจะทำคือมีคลาสนั้น แต่ลบสมาชิกแบบสมาชิกทั้งหมด ตกลงนี่ไม่ใช่สิ่งที่ต้องทำ แต่ฉันแนะนำ จริงๆคุณเพิ่งเริ่มต้นชั้นเรียนเช่นชั้นเรียนปกติและ PASS ตัวชี้เข้าไม่ต้องบอก ClassIWant.APtr () LetMeChange.ANYTHINGATALL (). andhave_no_structure ()

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


1

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

กราฟิก:

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

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

นี่เป็นการแยกสิ่งต่าง ๆ ออกเพื่อให้คุณสามารถเพิ่มแคชใหม่โดยไม่ต้องเข้าไปและแก้ไขวัตถุ Uber-Cache YMMV IANAL อื่น ๆ


1

สายไปงานเลี้ยงแต่ทว่าอย่างไรก็ตาม

Singleton เป็นเครื่องมือในกล่องเครื่องมือเช่นเดียวกับสิ่งอื่น หวังว่าคุณจะมีกล่องเครื่องมือของคุณมากกว่าค้อนเดียว

พิจารณาสิ่งนี้:

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

VS

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

กรณีที่ 1 นำไปสู่การมีเพศสัมพันธ์สูง ฯลฯ วิธีที่ 2 ไม่มีปัญหา @Aaronaught กำลังอธิบายเท่าที่ฉันสามารถบอกได้ มันคือทั้งหมดที่เกี่ยวกับวิธีที่คุณใช้


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

บางครั้ง Di เป็น overkill สำหรับงานที่ได้รับ วิธีการในรหัสตัวอย่างอาจเป็นตัวสร้าง แต่ไม่จำเป็น - โดยไม่ต้องดูตัวอย่างที่เป็นรูปธรรมมันเป็นอาร์กิวเมนต์ที่สงสัย นอกจากนี้ DoSomething ยังสามารถใช้ ISomething ได้และ MySingleton สามารถใช้อินเทอร์เฟซนั้นได้ - โอเคนั่นไม่ใช่ในตัวอย่าง .. แต่เป็นเพียงตัวอย่างเท่านั้น
Evgeni

1

ให้แต่ละหน้าจอใช้ตัวจัดการในตัวสร้าง

เมื่อคุณเริ่มแอพคุณจะสร้างอินสแตนซ์หนึ่งของผู้จัดการและส่งต่อไป

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

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

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