ทางเลือกอื่นของ Singleton คืออะไร


114

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

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

คำตอบ:


131

บล็อกทดสอบของ Googleมีชุดของรายการที่เกี่ยวกับการหลีกเลี่ยงโทน (เพื่อสร้างรหัสทดสอบ) สิ่งนี้อาจช่วยคุณได้:

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

ในระยะสั้นเราย้ายผู้ประกอบการรายใหม่ทั้งหมดไปที่โรงงาน เราจัดกลุ่มวัตถุที่มีอายุการใช้งานใกล้เคียงกันทั้งหมดไว้ในโรงงานเดียว


3
*** การใช้การฉีดแบบพึ่งพาเพื่อหลีกเลี่ยงเสื้อกล้าม
จัสติน

บทความเหล่านี้ดีพอ ๆ กับมาตรฐานการเขียนโปรแกรม C ++ ของ Google!

2
ดีไม่จริง คำแนะนำ 'อย่าใช้วิธีการคงที่' ตรงกับหลักการเชื่อมต่อขั้นต่ำของ Scott Meyers / Herb Sutters มีคำแนะนำที่เป็นประโยชน์ แต่ขาดการมีส่วนร่วมของจิตใจที่หลากหลาย
Matthieu M.

@ FrankS ทำไมคุณเปลี่ยนลำดับของลิงค์? มันเป็นไปตามลำดับเวลาที่ดีในตอนแรก
cregox

@Cawas ไม่รู้จริง ๆ นั่นก็มากกว่าสี่ปีที่แล้วดังนั้นฉันคิดว่าฉันมีเหตุผลบางอย่างในตอนนั้น :-)
FrankS

15

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

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


1
ฉันไม่เห็นด้วยกับข้อความ "วิธีที่ดีที่สุด" แต่ +1 สำหรับทางเลือกที่ดี
tylermac

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

2
@EastGhostCom: เราอาจใช้ซิงเกิลตันและหยุดพยายามทำสิ่งที่ยากสำหรับตัวเอง :)
gbjbaanb

5

ฉันอาจจะระบุสิ่งที่ชัดเจนที่นี่ แต่มีเหตุผลที่คุณไม่สามารถใช้กรอบการพึ่งพาการฉีดเช่นSpringหรือGuice ได้หรือไม่? (ฉันเชื่อว่า Spring ก็มีให้บริการสำหรับ. NET แล้วเช่นกัน)

ด้วยวิธีนี้เฟรมเวิร์กสามารถเก็บสำเนาของวัตถุการกำหนดค่าได้เพียงชุดเดียวและถั่วของคุณ (บริการ DAO หรืออะไรก็ได้) ไม่ต้องกังวลกับการค้นหา

นี่คือแนวทางที่ฉันมักจะทำ!


4

หากคุณใช้Spring Frameworkคุณก็สามารถสร้างถั่วธรรมดาได้ โดยค่าเริ่มต้น (หรือถ้าคุณกำหนดอย่างชัดเจนscope="singleton") getBean()เพียงหนึ่งตัวอย่างของถั่วถูกสร้างขึ้นและอินสแตนซ์ที่ถูกส่งกลับเวลาถั่วที่ใช้ในการพึ่งพาหรือดึงผ่านทุก

คุณจะได้รับประโยชน์จากอินสแตนซ์เดียวโดยไม่มีการเชื่อมต่อของรูปแบบ Singleton


3
โอ้ประชด - ใช้ (ซิงเกิลตัน) ถั่วสปริงเพื่อแทนที่ซิงเกิลตันของคุณ ...
Zack Macomber

4

อีกทางเลือกหนึ่งคือการส่งผ่านสิ่งที่คุณต้องการแทนที่จะถามสิ่งต่างๆ


4

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

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

ลอง refactoring รหัสของคุณเพื่อหลีกเลี่ยงการร่วมกันทั่วโลกและใหญ่Configurationวัตถุ ส่งผ่านพารามิเตอร์ที่จำเป็นไปยังคลาสไคลเอนต์เท่านั้น:

class Server {

    int port;

    Server(Configuration config) {
        this.port = config.getServerPort();
    } 

}

ควรปรับโครงสร้างใหม่เป็น:

 class Server {

    public Server(int port) {
       this.port = port;
    }
 }

กรอบพึ่งพาการฉีดจะช่วยได้มากที่นี่ แต่มันไม่จำเป็นต้อง stricly


ใช่มันเป็นจุดที่ดีจริงๆ ฉันได้ทำสิ่งนี้มาก่อน อ็อบเจ็กต์คอนฟิกูเรชันขนาดใหญ่ของฉันกำลังใช้อินเทอร์เฟซเช่น MailServiceConf, ServerConf .. กว่าเฟรมเวิร์ก Dependency Injection จะส่งผ่านการกำหนดค่าไปยังคลาสดังนั้นคลาสของฉันจึงไม่ขึ้นอยู่กับอ็อบเจ็กต์คอนฟิกูเรชันขนาดใหญ่
caltuntas

1

คุณสามารถทำพฤติกรรมเดียวกันของซิงเกิลตันได้โดยใช้วิธีการคงที่ สตีฟ Yegge อธิบายได้ดีมากในนี้โพสต์


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

0

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


1
ถ้าคลาสไร้สถานะควรเป็นคลาสคงที่
AlbertoPL

1
อยู่ใน C ++ - รูปแบบนี้เรียกว่า Monostate

0

ขึ้นอยู่กับว่ามีการใช้เครื่องมือ / เฟรมเวิร์กอะไรบ้าง ด้วยการพึ่งพาเครื่องมือ injection / ioc เรามักจะยังคงได้รับประสิทธิภาพ / การเพิ่มประสิทธิภาพแบบซิงเกิลตันโดยการให้คอนเทนเนอร์ di / ioc ใช้พฤติกรรมเดี่ยวสำหรับคลาสที่ต้องการ - (เช่นอินเทอร์เฟซ IConfigSettings) โดยสร้างเพียงอินสแตนซ์เดียวของคลาส สิ่งนี้ยังสามารถทดแทนการทดสอบได้

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


0

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

MyReuseCode.Configure(IConfiguration)

รหัสเริ่มต้นระบบจะมีลักษณะ:

Library.init(MyIConfigurationImpl)

0

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


0

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

public static Singleton getInstance() {
    if(singleton != null)
        createSingleton();
        return singleton;
    }
}

คุณสามารถโทรcreateSingleton(Information info)ได้โดยตรงเมื่อเริ่มต้นแอปพลิเคชัน (และใน setUp-Methods ของการทดสอบหน่วย)


-1

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

DI ที่ใช้ Spring เป็นต้นเป็นตัวเลือกที่ดีมาก แต่ไม่ใช่ตัวเลือกเดียว

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