การปฏิบัติที่ดีหรือไม่ดี? การเริ่มต้นวัตถุในทะเยอทะยาน


167

ฉันมีนิสัยแปลก ๆ ... อย่างน้อยตามที่เพื่อนร่วมงานของฉัน เราทำงานร่วมกันในโครงการขนาดเล็กด้วยกัน วิธีที่ฉันเขียนคลาสคือ (ตัวอย่างง่าย ๆ ):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

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

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

มีการคัดค้านการปฏิบัตินี้นอกเหนือจากความชอบส่วนตัวหรือไม่?

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

จุดด้อย:

  • หัวข้อความปลอดภัย
  • ไม่ปฏิบัติตามคำขอ "setter" เมื่อค่าที่ส่งเป็นค่าว่าง
  • Micro-การเพิ่มประสิทธิภาพ
  • การจัดการข้อยกเว้นควรเกิดขึ้นในตัวสร้าง
  • ต้องตรวจสอบรหัส null ในชั้นเรียน

ข้อดี:

  • Micro-การเพิ่มประสิทธิภาพ
  • คุณสมบัติไม่ส่งคืนค่าว่าง
  • เลื่อนหรือหลีกเลี่ยงการโหลดวัตถุ "หนัก"

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

การปรับปรุงครั้งล่าสุด:

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


14
นี่เป็นแพทเทิร์นที่ขี้เกียจมันไม่ได้ให้ประโยชน์ที่ยอดเยี่ยมกับคุณอย่างแน่นอน แต่มันก็ยังเป็นสิ่งที่ดีสำหรับฉัน
Machinarius

28
Lazy instantiation นั้นสมเหตุสมผลถ้าคุณมีผลกระทบต่อประสิทธิภาพที่วัดได้หรือหากสมาชิกเหล่านั้นไม่ค่อยได้ใช้และใช้หน่วยความจำจำนวนมากเกินไปหรือหากใช้เวลานานในการสร้างอินสแตนซ์พวกเขาและต้องการทำตามความต้องการเท่านั้น ในอัตราใดให้แน่ใจว่าบัญชีสำหรับปัญหาความปลอดภัยหัวข้อ (รหัสปัจจุบันของคุณไม่ได้ ) และพิจารณาการใช้ที่มีให้ขี้เกียจ <T>ระดับ
Chris Sinclair

10
ฉันคิดว่าคำถามนี้เหมาะกับcodereview.stackexchange.com
Eduardo Brites

7
@PLB ไม่ใช่รูปแบบซิงเกิล
โคลินแม็คเคย์

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

คำตอบ:


170

สิ่งที่คุณมีอยู่ที่นี่คือการใช้ "การเริ่มต้นขี้เกียจ" อย่างไร้เดียงสา

คำตอบสั้น ๆ :

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

ความเป็นมาและคำอธิบาย:

การใช้งานที่เป็นรูปธรรม: ก่อนอื่น
เรามาดูตัวอย่างที่เป็นรูปธรรมของคุณก่อน

  1. มันละเมิดหลักการอย่างน้อย Surprise (POLS) เมื่อค่าถูกกำหนดให้กับคุณสมบัติคาดว่าค่านี้จะถูกส่งคืน ในการดำเนินการของคุณนี่ไม่ใช่กรณีnull:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
    
  2. มันแนะนำปัญหาการเธรดค่อนข้างมาก: ผู้เรียกสองคนfoo.Barในเธรดที่แตกต่างกันอาจได้รับสองอินสแตนซ์ที่แตกต่างกันBarและหนึ่งในนั้นจะไม่มีการเชื่อมต่อกับFooอินสแตนซ์ การเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นกับBarอินสแตนซ์นั้นจะหายไปอย่างเงียบ ๆ
    นี่เป็นอีกกรณีหนึ่งของการละเมิด POLS เมื่อเข้าถึงค่าที่เก็บไว้ของคุณสมบัติเท่านั้นก็คาดว่าจะปลอดภัยต่อเธรด ในขณะที่คุณสามารถยืนยันได้ว่าคลาสนั้นไม่ได้ปลอดภัยสำหรับเธรด - รวมถึงผู้ได้รับทรัพย์สินของคุณ - คุณจะต้องจัดทำเอกสารให้ถูกต้องเนื่องจากไม่ใช่กรณีปกติ นอกจากนี้การแนะนำของปัญหานี้ไม่จำเป็นอย่างที่เราจะเห็นในไม่ช้า

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

อย่างไรก็ตามโดยทั่วไปคุณสมบัติดังกล่าวจะไม่มีตัวตั้งค่าซึ่งกำจัดปัญหาแรกที่ชี้ไปด้านบน
นอกจากนี้ยังมีการนำการใช้งานเธรดที่ปลอดภัยมาใช้เช่นLazy<T>- เพื่อหลีกเลี่ยงปัญหาที่สอง

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

  1. การสร้างวัตถุอาจไม่สำเร็จส่งผลให้เกิดข้อยกเว้นจากตัวรับทรัพย์สิน นี่เป็นอีกการละเมิดของ POLS ดังนั้นควรหลีกเลี่ยง แม้แต่ส่วนที่เกี่ยวกับคุณสมบัติใน "แนวทางการออกแบบสำหรับการพัฒนา Class Library" ระบุอย่างชัดเจนว่าผู้ได้รับทรัพย์สินไม่ควรโยนข้อยกเว้น:

    หลีกเลี่ยงการโยนข้อยกเว้นจากผู้ได้รับทรัพย์สิน

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

  2. การปรับให้เหมาะสมอัตโนมัติโดยคอมไพเลอร์ได้รับผลกระทบคือการคาดการณ์แบบอินไลน์และการแยกสาขา โปรดดูคำตอบของ Bill Kสำหรับคำอธิบายโดยละเอียด

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

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


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

  1. การทำให้เป็นอันดับ:
    สถานะ EricJ ในความคิดเห็นเดียว:

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

    มีปัญหาหลายอย่างกับอาร์กิวเมนต์นี้:

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

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

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

4
@JohnWillemse: นั่นเป็นปัญหาของสถาปัตยกรรมของคุณ คุณควรปรับโครงสร้างชั้นเรียนของคุณใหม่ในแบบที่เล็กลงและเน้นให้มากขึ้น อย่าสร้างหนึ่งคลาสสำหรับ 5 สิ่ง / งานที่แตกต่างกัน สร้าง 5 คลาสแทน
Daniel Hilgarth

26
@JohnWillemse อาจพิจารณากรณีนี้ของการเพิ่มประสิทธิภาพก่อนวัยอันควรแล้ว ถ้าคุณไม่มีคอขวดของประสิทธิภาพ / หน่วยความจำที่วัดได้ฉันขอแนะนำให้ใช้เพราะมันจะเพิ่มความซับซ้อนและแนะนำปัญหาการเธรด
Chris Sinclair

2
+1 นี่ไม่ใช่ตัวเลือกการออกแบบที่ดีสำหรับ 95% ของคลาส การเริ่มต้น Lazy มีข้อดี แต่ไม่ควรใช้กับคุณสมบัติทั้งหมด มันเพิ่มความซับซ้อน, ความยากในการอ่านรหัส, ปัญหาด้านความปลอดภัยของเธรด ... สำหรับการเพิ่มประสิทธิภาพที่มองไม่เห็นใน 99% ของกรณี นอกจากนี้ตามที่ SolutionYogi กล่าวว่าเป็นความคิดเห็นรหัส OP ของรถนั้นเป็นข้อพิสูจน์ว่ารูปแบบนี้ไม่สำคัญที่จะใช้
ken2k

2
@DanielHilgarth ขอบคุณที่ทำทุกอย่างเพื่อจดบันทึก (เกือบ) ทุกอย่างที่ผิดกับการใช้รูปแบบนี้โดยไม่มีเงื่อนไข เยี่ยมมาก!
อเล็กซ์

1
@DanielHilgarth ใช่และไม่ใช่ การละเมิดเป็นปัญหาที่นี่ดังนั้นใช่ แต่ก็ยังมี 'ไม่' เพราะ POLS เป็นอย่างเคร่งครัดหลักการที่ว่าคุณอาจจะไม่ประหลาดใจโดยรหัส หาก Foo ไม่ปรากฏตัวนอกโปรแกรมของคุณเป็นความเสี่ยงที่คุณสามารถทำได้หรือไม่ ในกรณีนี้ฉันเกือบจะรับประกันได้ว่าในที่สุดคุณจะประหลาดใจเพราะคุณไม่ได้ควบคุมวิธีการเข้าถึงคุณสมบัติ ความเสี่ยงกลายเป็นข้อผิดพลาดและการโต้แย้งของคุณเกี่ยวกับnullคดีนี้รุนแรงขึ้นมาก :-)
atlaste

49

มันเป็นตัวเลือกการออกแบบที่ดี แนะนำอย่างยิ่งสำหรับรหัสห้องสมุดหรือชั้นเรียนหลัก

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

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

ประการที่สองทรัพยากรจะได้รับการสร้างถ้าจำเป็นเท่านั้น

ประการที่สามคุณหลีกเลี่ยงการรวบรวมขยะวัตถุที่ไม่ได้ใช้

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

มีข้อยกเว้นสำหรับกฎนี้

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

แนวทางการออกแบบสำหรับการพัฒนาคลาสไลบรารีที่http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx

เกี่ยวกับ Lazy<T>

ทั่วไปLazy<T>ชั้นถูกสร้างขึ้นว่าสำหรับสิ่งที่โปสเตอร์ต้องการดูขี้เกียจเริ่มต้นที่http://msdn.microsoft.com/en-us/library/dd997286(v=vs.100).aspx หากคุณมี. NET รุ่นเก่ากว่าคุณต้องใช้รูปแบบรหัสที่แสดงในคำถาม รูปแบบรหัสนี้เป็นเรื่องธรรมดามากที่ Microsoft เห็นว่าเหมาะสมที่จะรวมคลาสในไลบรารี. NET ล่าสุดเพื่อให้ง่ายต่อการใช้รูปแบบ นอกจากนี้หากการปรับใช้ของคุณต้องการความปลอดภัยของเธรดคุณต้องเพิ่มมัน

ชนิดข้อมูลดั้งเดิมและคลาสอย่างง่าย

Obvioulsy List<string>คุณจะไม่ได้ไปใช้ขี้เกียจเริ่มต้นสำหรับชนิดข้อมูลดั้งเดิมหรือการใช้งานระดับง่ายๆเช่น

ก่อนแสดงความคิดเห็นเกี่ยวกับ Lazy

Lazy<T> เป็นที่รู้จักใน. NET 4.0 ดังนั้นโปรดอย่าเพิ่มความคิดเห็นเกี่ยวกับชั้นนี้อีก

ก่อนแสดงความคิดเห็นเกี่ยวกับ Micro-Optimisation

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

เกี่ยวกับอินเทอร์เฟซผู้ใช้

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

"Setters"

ฉันไม่เคยใช้ set-property ("setters") สำหรับทรัพย์สินที่โหลดขี้เกียจ foo.Bar = null;ดังนั้นคุณจะไม่อนุญาตให้ หากคุณต้องการตั้งค่าBarฉันจะสร้างวิธีการที่เรียกว่าSetBar(Bar value)และไม่ใช้การเริ่มต้นขี้เกียจ

คอลเลกชัน

คุณสมบัติการรวบรวมคลาสจะถูกเตรียมใช้งานเมื่อประกาศเสมอเนื่องจากไม่ควรเป็นค่าว่าง

ชั้นเรียนที่ซับซ้อน

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

ในที่สุด

ฉันไม่เคยบอกว่าจะทำเช่นนี้สำหรับทุกชั้นเรียนหรือทุกกรณี มันเป็นนิสัยที่ไม่ดี


6
หากคุณสามารถเรียก foo.Bar ได้หลายครั้งในหลาย ๆ เธรดโดยไม่ต้องทำการตั้งค่าใด ๆ แต่รับค่าต่างกันแสดงว่าคุณมีคลาสที่มีหมัดต่ำ
Lie Ryan

25
ฉันคิดว่านี่เป็นกฎง่ายๆที่ไม่มีข้อควรพิจารณามากมาย นอกจากแถบเป็นหมูทรัพยากรรู้นี่คือการเพิ่มประสิทธิภาพขนาดเล็กที่ไม่จำเป็น และในกรณีที่ Bar นั้นมีทรัพยากรสูงมีเธรดที่ปลอดภัย Lazy <T> อยู่ใน. net
Andrew Hanlon

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

20
ฉันกังวลจริงๆว่าผู้พัฒนารายอื่นจะเห็นคำตอบนี้และคิดว่านี่เป็นแนวทางปฏิบัติที่ดี นี่เป็นวิธีปฏิบัติที่แย่มาก ๆ ถ้าคุณใช้มันโดยไม่มีเงื่อนไข นอกจากสิ่งที่ได้กล่าวไปแล้วคุณกำลังทำให้ชีวิตของทุกคนยากขึ้นมาก (สำหรับนักพัฒนาลูกค้าและสำหรับนักพัฒนาด้านบำรุงรักษา) เพื่อผลประโยชน์เพียงเล็กน้อย (ถ้าได้รับเลย) คุณควรได้ยินจากข้อดี: Donald Knuth ในซีรี่ส์ The Art of Computer Programming มีชื่อเสียงกล่าวว่า "การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด" สิ่งที่คุณกำลังทำไม่ใช่แค่ชั่วร้ายเท่านั้น
อเล็กซ์

4
มีตัวบ่งชี้มากมายที่คุณเลือกคำตอบที่ผิด (และการตัดสินใจเขียนโปรแกรมผิด) คุณมีข้อเสียมากกว่าข้อดีในรายการของคุณ คุณมีคนต่อกรกับมันมากกว่านั้น สมาชิกที่มีประสบการณ์มากขึ้นของเว็บไซต์นี้ (@BillK และ @DanielHilgarth) ที่โพสต์ในคำถามนี้ขัดต่อมัน เพื่อนร่วมงานของคุณบอกคุณแล้วว่ามันผิด อย่างจริงจังมันผิด! ถ้าฉันจับหนึ่งในนักพัฒนาของทีมของฉัน (ฉันเป็นหัวหน้าทีม) ทำเช่นนี้เขาจะใช้เวลา 5 นาทีแล้วได้รับการสอนว่าทำไมเขาไม่ควรทำเช่นนี้
อเล็กซ์

17

คุณพิจารณาการนำรูปแบบดังกล่าวไปใช้Lazy<T>หรือไม่?

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

อย่างที่คนอื่นพูดคุณจะโหลดวัตถุอย่างเกียจคร้านถ้าพวกมันหนักทรัพยากรจริง ๆ หรือใช้เวลาโหลดในระหว่างการสร้างวัตถุ


ขอบคุณฉันเข้าใจแล้วและฉันจะตรวจสอบอย่างแน่นอนในLazy<T>ตอนนี้และละเว้นจากการใช้วิธีที่ฉันมักจะทำ
John Willemse

1
คุณไม่ได้รับความปลอดภัยด้ายเวทมนตร์ ... คุณยังต้องคิดเกี่ยวกับมัน จาก MSDN:Making the Lazy<T> object thread safe does not protect the lazily initialized object. If multiple threads can access the lazily initialized object, you must make its properties and methods safe for multithreaded access.
Eric J.

@EricJ แน่นอนว่าแน่นอน คุณจะได้รับความปลอดภัยของเธรดเมื่อเริ่มต้นวัตถุ แต่ภายหลังคุณต้องจัดการกับการซิงโครไนซ์เป็นวัตถุอื่น ๆ
Matías Fidemraizer

9

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

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


ขอบคุณ! นั่นทำให้รู้สึก
John Willemse

9

ข้อเสียที่ฉันเห็นคือถ้าคุณต้องการถามว่า Bars เป็นโมฆะมันจะไม่มีทางเป็นไปได้และคุณจะสร้างรายการที่นั่น


ฉันไม่คิดว่ามันเป็นข้อเสีย
Peter Porfy

ทำไมข้อเสียคือ? เพียงแค่ตรวจสอบใด ๆ แทนที่จะเป็นโมฆะ if (! Foo.Bars.Any ())
s.meijer

6
@PeterPorfy: มันละเมิดPOLS คุณใส่nullแต่ไม่ได้รับกลับ โดยปกติคุณสมมติว่าคุณได้รับค่าเดียวกันกับที่คุณใส่ไว้ในคุณสมบัติ
Daniel Hilgarth

@DanielHilgarth ขอบคุณอีกครั้ง นั่นเป็นข้อโต้แย้งที่ถูกต้องมากซึ่งฉันไม่เคยพิจารณามาก่อน
John Willemse

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

8

Lazy instantiation / initialization เป็นรูปแบบที่เหมาะสมที่สุด อย่างไรก็ตามโปรดจำไว้ว่าในฐานะผู้บริโภคทั่วไปของ API ของคุณไม่คาดหวังว่าผู้ที่ได้รับและผู้ตั้งค่าจะใช้เวลามองเห็นได้จากผู้ใช้ POV (หรือล้มเหลว)


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

8

ฉันแค่แสดงความคิดเห็นต่อคำตอบของ Daniel แต่ฉันคิดว่ามันคงไม่ดีพอ

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

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

คลาส SafeClass
{
    String name = "";
    จำนวนเต็มอายุ = 0;

    โมฆะสาธารณะ setName (สตริงชื่อใหม่)
    {
        ยืนยัน (newName! = null)
        ชื่อ = newname;
    } // ปฏิบัติตามรูปแบบนี้ตามอายุ
    ...
    String สาธารณะ toString () {
        String s = "Safe Class มีชื่อ:" + name + "และอายุ:" + age
    }
}

ด้วยรูปแบบของคุณเมธอด toString จะมีลักษณะดังนี้:

    if (name == null)
        โยน IllegalStateException ใหม่ ("SafeClass เข้าสู่สถานะที่ผิดกฎหมาย! ชื่อเป็นโมฆะ")
    ถ้า (age == null)
        โยน IllegalStateException ใหม่ ("SafeClass เข้าสู่สถานะที่ผิดกฎหมาย! อายุเป็นโมฆะ")

    String สาธารณะ toString () {
        String s = "Safe Class มีชื่อ:" + name + "และอายุ:" + age
    }

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

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

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

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


ขอบคุณสำหรับข้อมูลของคุณ ในกรณีของฉันไม่มีคลาสใดที่มีวิธีการใดที่อาจต้องตรวจสอบค่าว่างดังนั้นจึงไม่ใช่ปัญหา ฉันจะพิจารณาข้อคัดค้านอื่น ๆ ของคุณ
John Willemse

ฉันไม่เข้าใจจริงๆ แสดงว่าคุณไม่ได้ใช้สมาชิกของคุณในคลาสที่จัดเก็บ - ซึ่งคุณเพิ่งใช้คลาสเป็นโครงสร้างข้อมูล หากเป็นกรณีนี้คุณอาจต้องการอ่านjavaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.htmlซึ่งมีคำอธิบายที่ดีเกี่ยวกับวิธีการปรับปรุงโค้ดของคุณโดยหลีกเลี่ยงการจัดการสถานะวัตถุภายนอก หากคุณกำลังจัดการกับสิ่งเหล่านี้ภายในคุณจะทำเช่นนั้นโดยไม่ตรวจสอบทุกอย่างเป็นโมฆะซ้ำ ๆ ?
Bill K

บางส่วนของคำตอบนี้เป็นสิ่งที่ดี โดยปกติเมื่อใช้รูปแบบนี้toString()จะเรียกgetName()ใช้ไม่ได้nameโดยตรง
Izkata

@BillK ใช่คลาสเป็นโครงสร้างข้อมูลขนาดใหญ่ งานทั้งหมดจะทำในคลาสคงที่ ฉันจะตรวจสอบบทความที่ลิงค์ ขอบคุณ!
John Willemse

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

4

ให้ฉันเพิ่มอีกหนึ่งคะแนนไปยังจุดดี ๆ ที่ทำโดยคนอื่น ...

ดีบักจะ ( โดยค่าเริ่มต้น ) ประเมินคุณสมบัติเมื่อก้าวผ่านรหัสซึ่งอาจจะยกตัวอย่างBarเร็วกว่าปกติที่จะเกิดขึ้นโดยเพียงแค่รันโค้ด กล่าวอีกนัยหนึ่งการกระทำการดีบั๊กกำลังเปลี่ยนการดำเนินการของโปรแกรม

สิ่งนี้อาจเป็นหรือไม่เป็นปัญหา (ขึ้นอยู่กับผลข้างเคียง) แต่เป็นสิ่งที่ต้องระวัง


2

คุณแน่ใจหรือไม่ว่า Foo ควรจะสร้างอะไรขึ้นมาบ้าง?

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

ถ้าอย่างไรก็ตามวัตถุประสงค์ของการเป็น Foo ก็คือการสร้างอินสแตนซ์ของ Type Bar ดังนั้นฉันไม่เห็นอะไรผิดปกติกับการทำอย่างเกียจคร้าน


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