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


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.