ความซับซ้อนของตัวสร้างควรจะเป็น


18

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

คนอื่นคิดอย่างไร ฉันใช้ C # หากสร้างความแตกต่าง

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

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


8
ฉันไม่เห็นด้วย. ดูหลักการผกผันของการพึ่งพามันจะดีกว่าถ้าวัตถุ A ถูกส่งไปยังวัตถุ B ในสถานะที่ถูกต้องในตัวสร้างมัน
Jimmy Hoffa

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

1
ฉันเห็นด้วยกับข้อเสนอแนะของ Jimmy Hoffa ในการดูการฉีดพึ่งพา (หรือ "การพึ่งพาการพึ่งพา") นั่นเป็นความคิดแรกของฉันในการอ่านคำอธิบายเพราะฟังดูเหมือนว่าคุณมีอยู่ครึ่งทางแล้ว คุณได้แบ่งการใช้งานออกเป็นคลาส A ซึ่งคลาส B ใช้อยู่แล้ว ฉันไม่สามารถพูดคุยกับกรณีของคุณได้ แต่โดยทั่วไปฉันพบว่ารหัสการเปลี่ยนโครงสร้างเพื่อใช้รูปแบบการฉีดที่พึ่งพาทำให้ชั้นเรียนมีความเรียบง่าย / ไม่พันกัน
ศิษย์ของ Dr. Wily

1
Bobson ฉันเปลี่ยนชื่อเป็น 'ควร' ฉันขอแนวปฏิบัติที่ดีที่สุดที่นี่
Taein Kim

1
@JimmyHoffa: บางทีคุณควรขยายความคิดเห็นของคุณเป็นคำตอบ
kevin cline

คำตอบ:


22

คำถามของคุณประกอบด้วยสองส่วนแยกจากกันทั้งหมด:

ฉันควรโยนข้อยกเว้นจากตัวสร้างหรือฉันควรมีวิธีการล้มเหลว?

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

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

ฉันสามารถเข้าถึง "ส่วนอื่น ๆ ของระบบเช่น DB" จากตัวสร้าง

ผมคิดว่านี่ไปกับหลักการของความประหลาดใจไม่น้อยกว่า มีคนไม่มากนักที่คาดว่านวกรรมิกจะเข้าถึงฐานข้อมูล ดังนั้นไม่คุณไม่ควรทำเช่นนั้น


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

@ JimmyHoffa ฉันไม่เห็นความแตกต่างระหว่างวิธีการสร้างและวิธีการเฉพาะสำหรับการก่อสร้าง
ร่าเริง

4
คุณอาจไม่ได้ แต่หลายคนทำ คิดเมื่อคุณสร้างวัตถุเชื่อมต่อคอนสตรัคเตอร์เชื่อมต่อหรือไม่ วัตถุนั้นสามารถใช้งานได้หากไม่ได้เชื่อมต่อ? ในคำถามทั้งสองคำตอบคือไม่เพราะจะมีประโยชน์ในการสร้างวัตถุเชื่อมต่อบางส่วนเพื่อให้คุณสามารถปรับแต่งการตั้งค่าและสิ่งต่าง ๆ ก่อนที่จะเปิดการเชื่อมต่อ นี่เป็นวิธีการทั่วไปในสถานการณ์นี้ ฉันยังคิดว่าการฉีดคอนสตรัคเตอร์เป็นวิธีการที่เหมาะสมสำหรับสถานการณ์ที่วัตถุ A มีการพึ่งพาทรัพยากร แต่วิธีการเปิดยังดีกว่าการให้คอนสตรัคเตอร์ทำงานที่เป็นอันตราย
Jimmy Hoffa

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

2
มันไม่ได้เป็นข้อกำหนด แต่เป็นการตัดสินใจออกแบบ พวกเขาอาจทำให้ตัวสร้างใช้สตริงการเชื่อมต่อและเชื่อมต่อทันทีเมื่อสร้าง พวกเขาตัดสินใจที่จะไม่มากเกินไปเพราะมันเป็นเรื่องที่เป็นประโยชน์เพื่อให้สามารถสร้างวัตถุแล้วร่างบิดออกสตริงการเชื่อมต่อหรือบิตอื่น ๆ และ bobbles และเชื่อมต่อในภายหลัง ,
จิมมี่ฮอฟฟา

19

ฮึ.

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

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

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


2
สถานการณ์การก่อสร้างที่ซับซ้อนเป็นเหตุผลว่าทำไมรูปแบบการสร้างสรรค์เช่นที่คุณพูดถึงอยู่ที่นี่ นี่เป็นวิธีการที่ดีในการสร้างปัญหาเหล่านี้ ฉันต้องคิดเกี่ยวกับวิธีการใน. NET Connectionวัตถุที่สามารถCreateCommandให้คุณเพื่อให้คุณรู้ว่าคำสั่งเชื่อมต่ออย่างเหมาะสม
Jimmy Hoffa

2
ในเรื่องนี้ฉันจะเพิ่มว่าฐานข้อมูลเป็นการพึ่งพาภายนอกอย่างมากสมมติว่าคุณต้องการเปลี่ยนฐานข้อมูลในบางจุดในอนาคต (หรือจำลองวัตถุสำหรับการทดสอบ) ย้ายรหัสประเภทนี้ไปยังคลาสโรงงาน (หรืออะไรที่คล้ายกับ IService ผู้ให้บริการ) ดูเหมือนจะเป็นวิธีที่สะอาดกว่านี้
Jason Sperske

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

1
ต้องลองจับข้อยกเว้นที่ทำให้เดือดดาลจากคอนสตรัคเตอร์ +1 ถึงข้อโต้แย้งนี้ ต้องทำงานกับคนที่พูดว่า "ไม่ทำงานในนวกรรมิก" แต่มันก็สามารถสร้างลวดลายแปลก ๆ ได้ ฉันได้เห็นโค้ดที่วัตถุทุกชิ้นไม่เพียง แต่เป็นคอนสตรัคเตอร์ แต่ยังมีรูปแบบของการเริ่มต้น () ที่เดาอะไร วัตถุไม่ถูกต้องจริงๆจนกว่าจะมีการเรียก และจากนั้นคุณมีสองสิ่งที่จะเรียก: ctor และ Initialize () ปัญหาของการทำงานเพื่อตั้งค่าวัตถุจะไม่หายไป - C # อาจให้ผลการแก้ปัญหาที่แตกต่างกว่าพูด C ++ โรงงานเป็นสิ่งที่ดี!
J Trana

1
@jtrana - ใช่เริ่มต้นไม่ดี ตัวสร้างเริ่มต้นให้เป็นค่าเริ่มต้นที่มีสติหรือโรงงานหรือสร้างวิธีการสร้างมันและส่งกลับตัวอย่างที่ใช้งานได้
Telastyn

1

คอนสตรัคเตอร์ - ความซับซ้อนของความซับซ้อนของวิธีการเป็นเรื่องของการอภิปรายเกี่ยวกับ stile ที่ดี บ่อยครั้งที่Class() {}มีการใช้Constructor ที่ว่างเปล่าด้วยวิธีการที่Init(..) {..}ซับซ้อนกว่า ท่ามกลางข้อโต้แย้งที่จะนำมาพิจารณา:

  • ความเป็นไปได้ของการจัดลำดับชั้น
  • ด้วยวิธีการแยก Init จากตัวสร้างคุณมักจะต้องทำซ้ำลำดับเดียวกันClass x = new Class(); x.Init(..);หลายครั้งบางส่วนในการทดสอบหน่วย อย่างไรก็ตามไม่ค่อยดีนักสำหรับการอ่าน บางครั้งฉันเพิ่งวางมันลงในหนึ่งบรรทัดของโค้ด
  • เมื่อเป้าหมายการทดสอบหน่วยเป็นเพียงการทดสอบการกำหนดค่าเริ่มต้นของคลาสให้ดีกว่าที่จะมี Init ของตัวเองเพื่อทำสิ่งที่ซับซ้อนมากขึ้นให้เป็นหนึ่งเดียวโดยใช้ Asserts ระดับกลาง

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