ตัวแปรระดับโลกที่ได้รับเกียรติ - กลายเป็นระดับโลกที่รุ่งโรจน์ บางคนบอกว่าการออกแบบเชิงวัตถุแตก
ให้ฉันสถานการณ์อื่น ๆ นอกเหนือจากคนตัดไม้เก่าที่ดีที่มันเหมาะสมที่จะใช้ซิงเกิล
ตัวแปรระดับโลกที่ได้รับเกียรติ - กลายเป็นระดับโลกที่รุ่งโรจน์ บางคนบอกว่าการออกแบบเชิงวัตถุแตก
ให้ฉันสถานการณ์อื่น ๆ นอกเหนือจากคนตัดไม้เก่าที่ดีที่มันเหมาะสมที่จะใช้ซิงเกิล
คำตอบ:
ในการสืบเสาะหาความจริงฉันค้นพบว่าจริงๆแล้วมีเหตุผล "ยอมรับ" น้อยมากที่ใช้ซิงเกิลตัน
เหตุผลหนึ่งที่มีแนวโน้มที่จะเกิดขึ้นซ้ำแล้วซ้ำอีกใน internets คือของคลาส "การบันทึก" (ซึ่งคุณกล่าวถึง) ในกรณีนี้สามารถใช้ซิงเกิลตันแทนอินสแตนซ์เดียวของคลาสได้เนื่องจากคลาสการบันทึกมักจะต้องใช้ซ้ำ ๆ ซ้ำไปทุกครั้งในโครงการ ถ้าทุกคลาสใช้คลาสการบันทึกนี้การฉีดพึ่งพาจะยุ่งยาก
การบันทึกเป็นตัวอย่างเฉพาะของซิงเกิล "ยอมรับ" เนื่องจากไม่มีผลต่อการเรียกใช้โค้ดของคุณ ปิดใช้งานการบันทึกรหัสการใช้งานจะยังคงเหมือนเดิม เปิดใช้งานเหมือนกัน Misko วางไว้ในวิธีต่อไปนี้ในRoot Cause ของ Singletons "ข้อมูลที่นี่ไหลทางเดียว: จากแอปพลิเคชันของคุณไปที่ logger แม้ว่า loggers จะเป็นสถานะโกลบอลเนื่องจากไม่มีข้อมูลไหลจาก loggers ลงในแอพพลิเคชัน
ฉันแน่ใจว่ามีเหตุผลที่ถูกต้องอื่น ๆ เช่นกัน อเล็กซ์มิลเลอร์ใน " Patterns I Hate " พูดถึงผู้ให้บริการและ UI ฝั่งลูกค้าก็อาจเป็นตัวเลือก "ยอมรับได้"
อ่านเพิ่มเติมที่ Singleton ฉันรักคุณ แต่คุณกำลังทำให้ฉันผิดหวัง
ผู้สมัคร Singleton จะต้องตอบสนองความต้องการที่สาม:
หากซิงเกิลที่เสนอของคุณมีข้อกำหนดเพียงหนึ่งหรือสองข้อการออกแบบใหม่มักเป็นตัวเลือกที่ถูกต้อง
ตัวอย่างเช่นตัวจัดคิวเครื่องพิมพ์ไม่น่าจะถูกเรียกจากที่เดียว (เมนูพิมพ์) ดังนั้นคุณสามารถใช้ mutexes เพื่อแก้ปัญหาการเข้าถึงพร้อมกัน
ตัวบันทึกอย่างง่ายคือตัวอย่างที่ชัดเจนที่สุดของซิงเกิลตันที่ถูกต้อง แต่สิ่งนี้สามารถเปลี่ยนแปลงได้ด้วยแผนการบันทึกที่ซับซ้อนมากขึ้น
การอ่านไฟล์การกำหนดค่าที่ควรอ่านได้ในเวลาที่เริ่มต้นและการห่อหุ้มพวกเขาใน Singleton
Properties.Settings.Default
ใน. NET
คุณใช้ซิงเกิลตันเมื่อคุณต้องการจัดการทรัพยากรที่ใช้ร่วมกัน ตัวอย่างเช่นตัวจัดคิวเครื่องพิมพ์ แอปพลิเคชันของคุณควรมีอินสแตนซ์เดียวของสพูลเลอร์เพื่อหลีกเลี่ยงคำขอที่ขัดแย้งกันสำหรับทรัพยากรเดียวกัน
หรือการเชื่อมต่อฐานข้อมูลหรือตัวจัดการไฟล์เป็นต้น
อ่านซิงเกิลตันเดียวที่เก็บสถานะโกลบอลบางอย่าง (ภาษาผู้ใช้, วิธีใช้ไฟล์พา ธ , เส้นทางแอปพลิเคชัน) นั้นสมเหตุสมผล ระมัดระวังการใช้ซิงเกิลตันเพื่อควบคุมตรรกะทางธุรกิจ - ซิงเกิลเกือบจะกลายเป็นหลาย ๆ
การจัดการการเชื่อมต่อ (หรือกลุ่มของการเชื่อมต่อ) ไปยังฐานข้อมูล
ฉันจะใช้มันเพื่อดึงและจัดเก็บข้อมูลในไฟล์การกำหนดค่าภายนอก
หนึ่งในวิธีที่คุณใช้ซิงเกิลตันคือครอบคลุมอินสแตนซ์ที่จะต้องมี "นายหน้า" ควบคุมการเข้าถึงทรัพยากร Singletons มีความสามารถในการบันทึกที่ดีเพราะนายหน้าเข้าถึงไฟล์ซึ่งสามารถเขียนได้เฉพาะ สำหรับสิ่งที่ต้องการเข้าสู่ระบบพวกเขาให้วิธีการเขียนไปยังสิ่งที่เป็นนามธรรมเช่นไฟล์บันทึก - คุณสามารถห่อกลไกการแคชไว้ในซิงเกิลของคุณ ฯลฯ ...
ลองนึกถึงสถานการณ์ที่คุณมีแอพพลิเคชั่นที่มี windows / threads / etc จำนวนมาก แต่ต้องการการสื่อสารเพียงจุดเดียว ฉันเคยใช้งานหนึ่งงานเพื่อควบคุมงานที่ฉันต้องการให้แอปพลิเคชันเปิดตัว ซิงเกิลมีหน้าที่รับผิดชอบในการจัดอันดับงานและแสดงสถานะของพวกเขาไปยังส่วนอื่น ๆ ของโปรแกรมที่สนใจ ในสถานการณ์แบบนี้คุณสามารถดูซิงเกิลตันเหมือนกับคลาส "เซิร์ฟเวอร์" ที่กำลังทำงานอยู่ภายในแอปพลิเคชันของคุณ ... HTH
ควรใช้ซิงเกิลตันเมื่อจัดการการเข้าถึงทรัพยากรที่ใช้ร่วมกันโดยแอปพลิเคชันทั้งหมดและจะเป็นการทำลายล้างที่อาจมีอินสแตนซ์จำนวนมากในคลาสเดียวกัน การทำให้แน่ใจว่าการเข้าถึงเธรดทรัพยากรที่ใช้ร่วมกันอย่างปลอดภัยเป็นหนึ่งในตัวอย่างที่ดีของการที่รูปแบบนี้มีความสำคัญ
เมื่อใช้ Singletons คุณควรตรวจสอบให้แน่ใจว่าคุณไม่ได้ปกปิดการพึ่งพาโดยบังเอิญ ในอุดมคติแล้วจะมีการตั้งค่า singletons (เช่นตัวแปรสแตติกส่วนใหญ่ในแอปพลิเคชัน) ในระหว่างการประมวลผลรหัสเริ่มต้นสำหรับแอปพลิเคชัน (Static void Main () สำหรับ C # executables, void main main) สำหรับ java executables คลาสอื่นทั้งหมดที่อินสแตนซ์ที่ต้องการ สิ่งนี้จะช่วยให้คุณรักษาการทดสอบได้
ฉันคิดว่าการใช้งานแบบเดี่ยวสามารถนึกถึงความสัมพันธ์แบบหนึ่งต่อหนึ่งในฐานข้อมูล หากคุณมีส่วนต่าง ๆ ของรหัสของคุณที่ต้องทำงานกับอินสแตนซ์เดียวของวัตถุนั่นคือที่ที่เหมาะสมที่จะใช้ singletons
ตัวอย่างการใช้งานจริงของ singleton สามารถพบได้ในTest :: Builderคลาสที่สนับสนุนโมดูลการทดสอบ Perl สมัยใหม่ทุกรุ่น Test :: Builder ซิงเกิลตันจัดเก็บและโบรกเกอร์สถานะและประวัติของกระบวนการทดสอบ (ผลการทดสอบในอดีตนับจำนวนการทดสอบที่เรียกใช้) รวมถึงสิ่งต่าง ๆ ที่กำลังจะเกิดขึ้นของการทดสอบ สิ่งเหล่านี้เป็นสิ่งจำเป็นในการประสานงานโมดูลทดสอบหลายชุดซึ่งเขียนโดยผู้เขียนหลายคนเพื่อทำงานร่วมกันในสคริปต์ทดสอบเดียว
ประวัติความเป็นมาของการทดสอบ :: ตัวสร้างของซิงเกิลตันคือการศึกษา การโทรnew()
จะให้วัตถุเดียวกันกับคุณเสมอ ก่อนอื่นข้อมูลทั้งหมดจะถูกเก็บไว้เป็นตัวแปรคลาสโดยไม่มีสิ่งใดในวัตถุนั้น สิ่งนี้ใช้ได้จนกระทั่งฉันต้องการทดสอบ Test :: Builder ด้วยตัวเอง จากนั้นฉันต้องการวัตถุ Test :: Builder สองตัวซึ่งหนึ่งการตั้งค่าเป็นดัมมี่เพื่อจับและทดสอบพฤติกรรมและผลลัพธ์ของมันและอีกอันหนึ่งเป็นวัตถุทดสอบจริง ณ จุดนั้น Test :: Builder ได้รับการ refactored เป็นวัตถุจริง วัตถุเดี่ยวถูกเก็บไว้เป็นข้อมูลระดับและnew()
จะกลับมันเสมอ create()
ถูกเพิ่มเข้ามาเพื่อสร้างวัตถุใหม่และเปิดใช้งานการทดสอบ
ขณะนี้ผู้ใช้ต้องการเปลี่ยนพฤติกรรมบางอย่างของ Test :: Builder ในโมดูลของตนเอง แต่ปล่อยให้อยู่คนเดียวในขณะที่ประวัติการทดสอบยังคงเหมือนกันในทุกโมดูลการทดสอบ สิ่งที่เกิดขึ้นตอนนี้คือการทดสอบเสาหิน :: ตัวสร้างวัตถุจะถูกแบ่งย่อยเป็นชิ้นเล็ก ๆ (ประวัติ, เอาต์พุต, รูปแบบ ... ) ด้วยอินสแตนซ์ของ Test :: Builder ที่รวบรวมพวกมันเข้าด้วยกัน ตอนนี้ทดสอบ :: ตัวสร้างไม่จำเป็นต้องเป็นซิงเกิลอีกต่อไป ส่วนประกอบเช่นประวัติสามารถเป็นได้ สิ่งนี้ทำให้ความจำเป็นที่ไม่ยืดหยุ่นของซิงเกิลตันลดลง ให้ความยืดหยุ่นแก่ผู้ใช้ในการผสมและจับคู่ชิ้นส่วน ตอนนี้วัตถุเดี่ยวขนาดเล็กสามารถเก็บข้อมูลได้โดยมีวัตถุที่มีการตัดสินใจว่าจะใช้งานอย่างไร นอกจากนี้ยังช่วยให้คลาสที่ไม่ใช่ Test :: Builder สามารถเล่นได้โดยใช้ประวัติ Test :: Builder และเอาท์พุทซิงเกิล
ดูเหมือนว่าจะมีการผลักดันและดึงข้อมูลระหว่างการประสานงานของข้อมูลและความยืดหยุ่นของพฤติกรรมซึ่งสามารถบรรเทาได้ด้วยการวางซิงเกิลตันไปรอบ ๆ ข้อมูลที่ใช้ร่วมกันกับจำนวนพฤติกรรมที่น้อยที่สุดเท่าที่จะเป็นไปได้
เมื่อคุณโหลดอ็อบเจ็กต์ Properties configuration จากฐานข้อมูลหรือไฟล์มันจะช่วยให้มันเป็น singleton; ไม่มีเหตุผลที่จะต้องอ่านข้อมูลคงที่ที่จะไม่เปลี่ยนแปลงในขณะที่เซิร์ฟเวอร์กำลังทำงานอยู่
ตามที่ทุกคนกล่าวว่าทรัพยากรที่ใช้ร่วมกัน - โดยเฉพาะสิ่งที่ไม่สามารถจัดการการเข้าถึงพร้อมกัน
ตัวอย่างหนึ่งที่ฉันเห็นคือ Lucene Search Index Writer
คุณสามารถใช้ Singleton เมื่อใช้รูปแบบสถานะ (ในลักษณะที่แสดงในหนังสือ GoF) นี่เป็นเพราะคลาสรัฐที่เป็นรูปธรรมไม่มีสถานะของตนเองและดำเนินการในรูปแบบของคลาสบริบท
คุณสามารถทำให้ Abstract Factory กลายเป็นซิงเกิลตันได้
setState()
ความรับผิดชอบในการตัดสินใจนโยบายการสร้างรัฐ ช่วยถ้าภาษาโปรแกรมของคุณรองรับเทมเพลตหรือยาชื่อสามัญ แทนที่จะใช้ Singleton คุณสามารถใช้รูปแบบMonostateโดยที่การสร้างอินสแตนซ์ของออบเจ็กต์สถานะกลายเป็นการใช้ซ้ำสถานะออบเจ็กต์โกลบอล / สแตติกเดียวกัน ไวยากรณ์สำหรับการเปลี่ยนสถานะอาจยังคงไม่เปลี่ยนแปลงเนื่องจากผู้ใช้ของคุณไม่จำเป็นต้องทราบว่าสถานะอินสแตนซ์เป็น Monostate
ทรัพยากรที่ใช้ร่วมกัน โดยเฉพาะอย่างยิ่งใน PHP คลาสฐานข้อมูลคลาสเทมเพลตและคลาสคลังเก็บตัวแปรส่วนกลาง ทั้งหมดจะต้องถูกใช้ร่วมกันโดยโมดูล / คลาสทั้งหมดที่ใช้งานตลอดรหัส
เป็นการใช้งานวัตถุอย่างแท้จริง -> คลาสเทมเพลตประกอบด้วยเทมเพลตของเพจที่ถูกสร้างขึ้นและจะได้รับการขึ้นรูปเพิ่มและเปลี่ยนแปลงโดยโมดูลที่เพิ่มไปยังเอาต์พุตของเพจ มันจะต้องถูกเก็บไว้เป็นอินสแตนซ์เดียวเพื่อให้สิ่งนี้สามารถเกิดขึ้นได้และจะไปสำหรับฐานข้อมูล ด้วยฐานข้อมูลที่แชร์แบบซิงเกิลคลาสของโมดูลทั้งหมดสามารถเข้าถึงคิวรีและเรียกใช้โดยไม่ต้องรันซ้ำ
คลังเก็บตัวแปรทั่วโลกซิงเกิลให้คลังเก็บตัวแปรส่วนกลางที่เชื่อถือได้และใช้งานได้ง่าย มันทำให้โค้ดของคุณดีขึ้นมาก ลองนึกภาพว่ามีค่าการกำหนดค่าทั้งหมดในอาร์เรย์ในซิงเกิลตันดังนี้:
$gb->config['hostname']
หรือมีค่าภาษาทั้งหมดในอาร์เรย์เช่น:
$gb->lang['ENTER_USER']
ในตอนท้ายของการเรียกใช้รหัสสำหรับหน้าคุณจะได้รับการพูดว่าเป็นผู้ใหญ่ตอนนี้:
$template
Singleton เป็น$gb
singleton ที่มีอาร์เรย์ lang สำหรับแทนที่และเอาต์พุตทั้งหมดถูกโหลดและพร้อมใช้งาน คุณเพียงแทนที่พวกเขาเป็นคีย์ที่มีอยู่ในค่าเพจของเทมเพลตวัตถุผู้ใหญ่และจากนั้นให้บริการแก่ผู้ใช้
ข้อได้เปรียบที่ยิ่งใหญ่ของการทำเช่นนี้คือคุณสามารถทำอะไรก็ได้ที่คุณชอบ คุณสามารถไพพ์ค่าภาษาทั้งหมดเป็น Google แปลภาษาหรือบริการแปลอื่น ๆ แล้วนำกลับคืนมาและเปลี่ยนเป็นภาษาที่พวกเขาแปล หรือคุณสามารถแทนที่ในโครงสร้างหน้าหรือสตริงเนื้อหาตามที่คุณต้องการ
มันอาจเป็นประโยชน์มากในการกำหนดค่าความกังวลโครงสร้างพื้นฐานที่เฉพาะเจาะจงเป็น singletons หรือตัวแปรทั่วโลก ตัวอย่างที่ฉันชอบคือ Dependency Injection framework ที่ใช้ singletons เพื่อทำหน้าที่เป็นจุดเชื่อมต่อกับเฟรมเวิร์ก
ในกรณีนี้คุณกำลังพึ่งพาโครงสร้างพื้นฐานเพื่อลดความซับซ้อนในการใช้ไลบรารีและหลีกเลี่ยงความซับซ้อนที่ไม่จำเป็น
ฉันใช้มันสำหรับวัตถุที่ห่อหุ้มพารามิเตอร์บรรทัดคำสั่งเมื่อจัดการกับโมดูลที่เสียบได้ โปรแกรมหลักไม่ทราบว่าพารามิเตอร์บรรทัดคำสั่งสำหรับโมดูลที่โหลด (และไม่เคยรู้ว่าโมดูลกำลังโหลดอยู่) เช่นโหลดหลัก A ซึ่งไม่ต้องการพารามิเตอร์ใด ๆ (ดังนั้นทำไมจึงควรใช้ตัวชี้ / อ้างอิง / อะไรก็ตามฉันไม่แน่ใจ - ดูเหมือนมลพิษ) จากนั้นโหลดโมดูล X, Y และ Z ของสิ่งเหล่านี้พูดพารามิเตอร์ X และ Z ต้องการ (หรือยอมรับ) ดังนั้นพวกเขาจึงโทรกลับไปที่บรรทัดคำสั่งซิงเกิลเพื่อบอกว่าพารามิเตอร์ใดที่ต้องยอมรับและรันไทม์ที่พวกเขาโทรกลับเพื่อตรวจสอบว่าผู้ใช้ระบุจริงหรือไม่ ของพวกเขา.
ในหลาย ๆ วิธีซิงเกิลตันสำหรับการจัดการพารามิเตอร์ CGI จะทำงานคล้ายกันถ้าคุณใช้เพียงหนึ่งกระบวนการต่อการสืบค้น (เมธอด mod_ * อื่น ๆ ไม่ได้ทำเช่นนี้ดังนั้นมันจะไม่ดีที่นั่น - ดังนั้นอาร์กิวเมนต์ที่บอกว่าคุณไม่ควร ' ไม่ใช้ซิงเกิลตันในโลก mod_cgi ในกรณีที่คุณพอร์ตไปยัง mod_perl หรืออะไรก็ตามในโลก)
ตัวอย่างที่มีรหัสบางที
ที่นี่ ConcreteRegistry เป็นเกมซิงเกิลในโป๊กเกอร์ที่ช่วยให้พฤติกรรมทุกอย่างขึ้นไปบนผังแพ็กเกจเข้าถึงได้เพียงไม่กี่อินเตอร์เฟสหลักของเกม (เช่นด้านหน้าของแบบจำลองมุมมองตัวควบคุมสภาพแวดล้อม ฯลฯ ):
http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html
เอ็ด
1 - ความคิดเห็นเกี่ยวกับคำตอบแรก:
ฉันไม่เห็นด้วยกับคลาส Logger แบบคงที่ สิ่งนี้สามารถนำไปใช้ได้จริงสำหรับการนำไปใช้ แต่ไม่สามารถเปลี่ยนได้สำหรับการทดสอบหน่วย คลาสแบบคงที่ไม่สามารถแทนที่ด้วยการทดสอบสองครั้ง หากคุณไม่ทดสอบหน่วยคุณจะไม่เห็นปัญหาที่นี่
2 - ฉันพยายามไม่สร้างซิงเกิลตันด้วยมือ ฉันเพิ่งสร้างวัตถุอย่างง่ายพร้อมคอนสตรัคเตอร์ที่อนุญาตให้ฉันใส่ผู้ทำงานร่วมกันลงในวัตถุ ถ้าฉันต้องการซิงเกิลตันฉันจะใช้เฟรมเวิร์ก inyection (Spring.NET, Unity for .NET, Spring สำหรับ Java) หรืออื่น ๆ
ILogger logger = Logger.SingleInstance();
เมื่อวิธีนี้เป็นแบบสแตติกและส่งคืนอินสแตนซ์ที่เก็บไว้แบบคงที่ของ ILogger คุณใช้ตัวอย่างของ "กรอบงานการฉีดพึ่งพา" ภาชนะ DI เกือบทั้งหมดเป็นแบบซิงเกิล การกำหนดค่าของพวกเขาจะถูกกำหนดแบบคงที่และเข้าถึงได้จาก / เก็บไว้ในที่สุดในส่วนติดต่อผู้ให้บริการเดียว