ต้องพึ่งพาการฉีดมาเป็นค่าใช้จ่ายของการห่อหุ้มหรือไม่?


128

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

นี่เป็นการเปิดเผยการพึ่งพาที่ถูกฉีดและละเมิดหลักการ OOP ของการห่อหุ้ม

ฉันถูกต้องในการระบุการแลกเปลี่ยนนี้หรือไม่? คุณจัดการกับปัญหานี้อย่างไร

โปรดดูคำตอบของคำถามด้านล่างด้วย


7
นี่เป็นคำถามที่ฉลาดมาก imho
dfa

5
การตอบคำถามนี้ก่อนต้องมีการโต้แย้งเกี่ยวกับความหมายของการห่อหุ้ม ;)
Jeff Sternal

2
การห่อหุ้มได้รับการดูแลโดยอินเตอร์เฟส พวกเขาเปิดเผยลักษณะสำคัญของวัตถุและซ่อนรายละเอียดเช่นการอ้างอิง สิ่งนี้ช่วยให้เราสามารถ 'เปิด' คลาสในระดับหนึ่งเพื่อให้การกำหนดค่าที่ยืดหยุ่นมากขึ้น
Lawrence Wagerfield

คำตอบ:


62

มีอีกวิธีหนึ่งในการดูปัญหานี้ที่คุณอาจพบว่าน่าสนใจ

เมื่อเราใช้การฉีด IoC / การพึ่งพาเราไม่ได้ใช้แนวคิด OOP เป็นที่ยอมรับว่าเราใช้ภาษา OO เป็น 'โฮสต์' แต่แนวคิดเบื้องหลัง IoC มาจากวิศวกรรมซอฟต์แวร์เชิงส่วนประกอบไม่ใช่ OO

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

โดยการใช้เทคนิคที่คล้ายกันในโปรแกรม OO ของเราผ่าน IoC เรามุ่งหวังที่จะทำให้โปรแกรมง่ายขึ้นในการกำหนดค่าและบำรุงรักษา การประกาศการพึ่งพา (เป็นพารามิเตอร์ตัวสร้างหรืออะไรก็ตาม) เป็นส่วนสำคัญของสิ่งนี้ การห่อหุ้มไม่ได้นำไปใช้จริง ๆ เช่นเดียวกับในส่วนประกอบ / บริการเชิงโลกไม่มี 'ประเภทการนำไปใช้' สำหรับรายละเอียดที่รั่วไหลออกมา

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


18
การห่อหุ้มนั้นไม่ได้เป็นเพียงคำศัพท์แฟนซี มันเป็นเรื่องจริงที่มีผลประโยชน์ที่แท้จริงและไม่สำคัญว่าคุณจะถือว่าโปรแกรมของคุณเป็น "component oriented" หรือ "object oriented" การห่อหุ้มควรจะปกป้องสถานะของวัตถุ / ส่วนประกอบ / บริการ / ของคุณไม่ว่าจะถูกเปลี่ยนแปลงในรูปแบบที่ไม่คาดคิดและ IoC จะเอาการป้องกันนี้ไปบางส่วนดังนั้นจึงมีการแลกเปลี่ยนกันอย่างแน่นอน
Ron Inbar

1
ข้อโต้แย้งที่ส่งผ่านคอนสตรัคเตอร์ยังคงอยู่ในขอบเขตของวิธีการที่คาดหวังซึ่งวัตถุอาจ "เปลี่ยน": พวกมันถูกเปิดเผยอย่างชัดเจนและค่าคงที่รอบตัวพวกเขาจะถูกบังคับใช้ การซ่อนข้อมูลเป็นคำที่ดีกว่าสำหรับประเภทของความเป็นส่วนตัวที่คุณอ้างถึง @RonInbar และมันก็ไม่จำเป็นว่าจะเป็นประโยชน์เสมอไป (มันทำให้พาสต้ายากที่จะทำให้ยุ่งเหยิง ;-))
Nicholas Blumhardt

2
ประเด็นทั้งหมดของ OOP ก็คือพาสต้ายุ่งเหยิงจะถูกแยกออกเป็นคลาสที่แยกจากกันและคุณจะต้องยุ่งกับมันเลยถ้ามันเป็นพฤติกรรมของคลาสนั้น ๆ ที่คุณต้องการแก้ไข (นี่คือวิธีที่ OOP ช่วยลดความซับซ้อน) คลาส (หรือโมดูล) encapsulates internals ในขณะที่เปิดเผยส่วนต่อประสานสาธารณะที่สะดวก (นี่คือวิธีที่ OOP นำมาใช้ซ้ำ) คลาสที่แสดงถึงการพึ่งพาผ่านอินเตอร์เฟสสร้างความซับซ้อนให้กับลูกค้าและนำมาใช้ซ้ำได้น้อยลง มันยังมีความเปราะบางมากกว่า
Neutrino

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

1
นี่เป็นอีกวิธีหนึ่งในการกำหนดกรอบปัญหา: ลองคิดดูถ้าแอสเซมบลี. NET เลือก "encapsulation" และไม่ได้ประกาศว่าแอสเซมบลีอื่นใดที่พวกเขาพึ่งพา มันจะเป็นสถานการณ์ที่บ้าคลั่งการอ่านเอกสารและหวังว่าจะมีบางสิ่งที่ใช้ได้หลังจากโหลดมัน การประกาศการพึ่งพาที่ระดับนั้นจะช่วยให้การจัดการเครื่องมืออัตโนมัติกับองค์ประกอบขนาดใหญ่ของแอป คุณต้องเหล่ตาเพื่อดูการเปรียบเทียบ แต่จะใช้แรงคล้ายกันในระดับองค์ประกอบ มีการแลกเปลี่ยนเป็นและ YMMV เช่นเคย :-)
นิโคลัส Blumhardt

29

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

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

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

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

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


2
จุดที่ดี ฉันแนะนำไม่ให้ใช้ @Autowired ในฟิลด์ส่วนตัว ทำให้ชั้นเรียนยากต่อการทดสอบ คุณจะฉีด mocks หรือ stubs อย่างไร?
lumpynose

4
ฉันไม่เห็นด้วย. DI ละเมิดการห่อหุ้มและสามารถหลีกเลี่ยงได้ ตัวอย่างเช่นโดยใช้ ServiceLocator ซึ่งไม่จำเป็นต้องรู้อะไรเกี่ยวกับคลาสไคลเอ็นต์ มันแค่ต้องรู้เกี่ยวกับการใช้งานของการพึ่งพา Foo แต่สิ่งที่ดีที่สุดในกรณีส่วนใหญ่คือใช้ตัวดำเนินการ "ใหม่"
Rogério

5
@Rogerio - กรอบ DI ใด ๆ ที่ทำหน้าที่เหมือนกับ ServiceLocator ที่คุณอธิบาย; ลูกค้าไม่ทราบว่ามีอะไรเฉพาะเกี่ยวกับการใช้งาน Foo และเครื่องมือ DI ไม่ทราบข้อมูลเฉพาะเกี่ยวกับลูกค้า และการใช้ "ใหม่" นั้นแย่กว่ามากสำหรับการละเมิด encapsulation เนื่องจากคุณจำเป็นต้องรู้ไม่เพียง แต่คลาสการใช้งานที่แน่นอน แต่ยังรวมถึงคลาสและอินสแตนซ์ที่แน่นอนของการพึ่งพาทั้งหมดที่ต้องการ
Andrzej Doyle

4
การใช้ "ใหม่" เพื่อยกระดับคลาสผู้ช่วยซึ่งมักจะไม่เปิดเผยแม้แต่สาธารณะจะส่งเสริมการห่อหุ้ม ทางเลือก DI คือทำให้คลาสผู้ช่วยเป็นสาธารณะและเพิ่มตัวสร้างสาธารณะหรือ setter ในคลาสไคลเอ็นต์ การเปลี่ยนแปลงทั้งสองจะทำลายการห่อหุ้มที่จัดทำโดยคลาสผู้ช่วยเดิม
Rogério

1
"มันเป็นคำถามที่ดี - แต่ในบางจุดการห่อหุ้มในรูปแบบที่บริสุทธิ์นั้นจำเป็นต้องถูกละเมิดหากวัตถุนั้นมีการพึ่งพาได้จริง" หลักฐานการก่อตั้งของคำตอบของคุณนั้นไม่จริง ในฐานะที่เป็น @ Rogérioระบุใหม่ขึ้นอยู่กับการพึ่งพาภายในและวิธีการอื่น ๆ ที่วัตถุภายใน satifies พึ่งพาของตัวเองไม่ได้ละเมิด encapsulation
Neutrino

17

นี่เป็นการเปิดเผยการพึ่งพาที่ถูกฉีดและละเมิดหลักการ OOP ของการห่อหุ้ม

ทุกอย่างเป็นการละเมิด encapsulation :) มันเป็นหลักการที่อ่อนโยนที่ต้องได้รับการปฏิบัติอย่างดี

ดังนั้นสิ่งที่ละเมิด encapsulation?

มรดกไม่

"เนื่องจากการสืบทอดทำให้คลาสย่อยมีรายละเอียดเกี่ยวกับการนำไปปฏิบัติของผู้ปกครองจึงมักจะกล่าวว่า 'การสืบทอดแบ่งการห่อหุ้ม'" (แก๊งสี่สี่ 1995: 19)

การเขียนโปรแกรมเชิงลักษณะ ไม่ ตัวอย่างเช่นคุณลงทะเบียน onMethodCall () การโทรกลับและให้โอกาสที่ดีในการฉีดโค้ดไปที่การประเมินวิธีการปกติการเพิ่มผลข้างเคียงที่แปลก ฯลฯ

ประกาศเพื่อนใน C ++ ไม่

ระดับ extention ในรูบีไม่ เพียงกำหนดวิธีการที่สตริงที่ไหนสักแห่งหลังจากที่กำหนดระดับสตริง

ดีมากของสิ่งที่ไม่

การห่อหุ้มเป็นหลักการที่ดีและสำคัญ แต่ไม่ใช่คนเดียว

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}

3
"นั่นคือหลักการของฉันและถ้าคุณไม่ชอบพวกเขา ... ดีฉันมีคนอื่น" (Groucho Marx)
Ron Inbar

2
ฉันคิดว่านี่เป็นประเด็น มันขึ้นอยู่กับการฉีดและการห่อหุ้ม ดังนั้นควรใช้การฉีดแบบพึ่งพาซึ่งจะให้ประโยชน์อย่างมาก มันเป็น DI ทุกที่ที่ทำให้ DI มีชื่อไม่ดี
Richard Tingle

ไม่แน่ใจว่าคำตอบนี้พยายามจะพูดอะไร ... มันเป็นการดีที่จะละเมิด encapsulation เมื่อทำ DI นั่นเป็น "ok เสมอ" เพราะมันจะถูกละเมิดอยู่ดีหรือว่า DI อาจเป็นเหตุผลในการละเมิด encapsulation ในบันทึกอื่น ๆ วันนี้ไม่จำเป็นต้องพึ่งพาตัวสร้างสาธารณะหรือคุณสมบัติเพื่อที่จะฉีดการพึ่งพา แต่เราสามารถแทรกเข้าไปในช่องที่มีคำอธิบายประกอบส่วนตัวซึ่งง่ายกว่า (รหัสน้อยกว่า) และคงการห่อหุ้มไว้ ดังนั้นเราสามารถใช้ประโยชน์จากหลักการทั้งสองในเวลาเดียวกัน
Rogério

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

13

ใช่ DI ละเมิด encapsulation (หรือเรียกอีกอย่างว่า "การซ่อนข้อมูล")

แต่ปัญหาที่แท้จริงเกิดขึ้นเมื่อนักพัฒนาใช้เป็นข้ออ้างในการละเมิดหลักการ KISS (Keep It Short and Simple) และ YAGNI (คุณไม่ต้องการมัน) หลักการ

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


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

5

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

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

ฉันคิดว่ามันเป็นอย่างนี้: ภาชนะบรรจุระบบฉีดพึ่งพาการละเมิด encapsulation แต่รหัสของคุณไม่ได้ ความจริงแล้วรหัสของคุณนั้น "ห่อหุ้ม" มากกว่าเดิม


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

1
การเพิ่มสิ่งที่ @ Rogérioพูดว่าอาจมีประสิทธิภาพมากกว่า ไม่ใช่ทุกคลาสที่เคยสร้างขึ้นในประวัติศาสตร์ของโลกที่จำเป็นต้องมีการพึ่งพาอาศัยของมันทุกตัวเพื่อยกระดับอายุการใช้งานของวัตถุ วัตถุที่ใช้ DI สูญเสียการควบคุมขั้นพื้นฐานที่สุดของการพึ่งพาของตนเองนั่นคือตลอดชีวิต
Neutrino

5

ขณะที่เจฟฟ์ sternal ชี้ให้เห็นในความคิดเห็นกับคำถามคำตอบคือทั้งหมดขึ้นอยู่กับว่าคุณกำหนดencapsulation

ดูเหมือนจะมีสองค่ายหลักของความหมายที่ห่อหุ้ม:

  1. ทุกอย่างที่เกี่ยวข้องกับวัตถุนั้นเป็นวิธีการในวัตถุ ดังนั้นFileวัตถุที่อาจจะมีวิธีการที่จะSave, Print, Display, ModifyTextฯลฯ
  2. วัตถุคือโลกเล็ก ๆ ของตัวเองและไม่ได้ขึ้นอยู่กับพฤติกรรมภายนอก

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

ดังนั้นการฉีดพึ่งพาจะทำลาย encapsulation ถ้าคุณใช้คำนิยามแรก แต่จริงๆแล้วฉันไม่รู้ว่าฉันชอบคำจำกัดความแรกหรือไม่ - ไม่ชัดเจน (ถ้าเป็นเช่นนั้น MS Word จะเป็นชั้นเรียนขนาดใหญ่)

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


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

4

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


1
เพราะ ... ไม่? ถ้าคุณมีตัวบันทึกและคุณมีคลาสที่ต้องการตัวบันทึกการส่งผ่านตัวบันทึกไปยังคลาสนั้นไม่ละเมิด encapsulation และนั่นคือทั้งหมดที่ฉีดพึ่งพา
jrockway

3
ฉันคิดว่าคุณเข้าใจผิดห่อหุ้ม ตัวอย่างเช่นใช้คลาสวันที่ไร้เดียงสา ภายในนั้นอาจมีตัวแปรอินสแตนซ์ของวันเดือนและปี ถ้าสิ่งเหล่านี้ถูกเปิดเผยว่าเป็น setters ธรรมดาที่ไม่มีตรรกะสิ่งนี้จะทำลาย encapsulation เนื่องจากฉันสามารถทำบางสิ่งบางอย่างเช่น set เดือนถึง 2 และวันที่ 31 ในทางกลับกันถ้า setters ฉลาดและตรวจสอบ invariants แล้วสิ่งต่าง ๆ ก็ดี . นอกจากนี้โปรดสังเกตว่าในรุ่นหลังฉันสามารถเปลี่ยนการจัดเก็บเป็นวันตั้งแต่ 1/1/1970 และไม่มีสิ่งใดที่ใช้อินเทอร์เฟซจำเป็นต้องระวังสิ่งนี้หากฉันได้เขียนวิธีการวัน / เดือน / ปีอย่างเหมาะสม
เจสันวัตคินส์

2
DI แน่นอนไม่ละเมิดการห่อหุ้ม / การซ่อนข้อมูล หากคุณเปลี่ยนการพึ่งพาภายในส่วนตัวเป็นสิ่งที่เปิดเผยในส่วนต่อประสานสาธารณะของชั้นเรียนจากคำจำกัดความที่คุณได้ทำลายการห่อหุ้มของการพึ่งพานั้น
Rogério

2
ฉันมีตัวอย่างที่เป็นรูปธรรมที่ฉันคิดว่าการห่อหุ้มนั้นถูกบุกรุกโดย DI ฉันมี FooProvider ที่ได้รับ "ข้อมูล foo" จากฐานข้อมูลและ FooManager ที่เก็บแคชและคำนวณเนื้อหาที่ด้านบนของผู้ให้บริการ ฉันให้ผู้บริโภครหัสของฉันไปที่ FooProvider อย่างไม่ถูกต้องเพื่อรับข้อมูลซึ่งฉันควรจะห่อหุ้มมันไว้เพื่อที่พวกเขาจะได้ทราบถึง FooManager เท่านั้น นี่เป็นจุดเริ่มต้นสำหรับคำถามเดิมของฉัน
urig

1
@Rogerio: ฉันจะยืนยันว่านวกรรมิกไม่ได้เป็นส่วนหนึ่งของส่วนต่อประสานสาธารณะเพราะมันถูกใช้เฉพาะในรูทองค์ประกอบ ดังนั้นการพึ่งพาเป็นเพียง "เห็น" โดยรากองค์ประกอบ ความรับผิดชอบเดียวของรูตองค์ประกอบคือการเชื่อมโยงการพึ่งพาเหล่านี้เข้าด้วยกัน ดังนั้นการใช้การสร้างคอนสตรัคชันไม่ทำลาย encapsulation ใด ๆ
Jay Sullivan

4

นี้คล้ายกับคำตอบ upvoted แต่ฉันต้องการคิดออกดัง - บางทีคนอื่นอาจเห็นสิ่งนี้ด้วย

  • คลาสสิค OO ใช้ตัวสร้างเพื่อกำหนดสาธารณะ "เริ่มต้น" สัญญาสำหรับผู้บริโภคในชั้นเรียน (ซ่อนรายละเอียดการใช้งานทั้งหมด; การห่อหุ้ม aka) สัญญานี้สามารถมั่นใจได้ว่าหลังจาก instantiation คุณมีวัตถุที่พร้อมใช้งาน (เช่นไม่มีขั้นตอนการเริ่มต้นเพิ่มเติมที่ต้องจดจำ (ผู้ใช้ลืม) โดยผู้ใช้

  • (คอนสตรัค) DI ปฏิเสธอย่างไม่อาจแยก encapsulation โดยรายละเอียดการดำเนินการตกเลือดผ่านอินเทอร์เฟซตัวสร้างสาธารณะนี้ ตราบใดที่เรายังคงพิจารณาตัวสร้างสาธารณะที่รับผิดชอบในการกำหนดสัญญาการเริ่มต้นสำหรับผู้ใช้เราได้สร้างการละเมิด encapsulation ที่น่ากลัว

ตัวอย่างเชิงทฤษฎี:

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

สมมติว่าการติดตั้ง Foo นี้โดยเฉพาะอาจต้องใช้IWidgetเพื่อทำงาน การสร้าง Constructor ของการพึ่งพานี้จะให้เราสร้าง Constructor เช่นFoo (ขนาด int, วิดเจ็ต IWidget)

สิ่งที่ทำให้ฉันหงุดหงิดเกี่ยวกับเรื่องนี้คือตอนนี้เรามีคอนสตรัคเตอร์ที่ผสมข้อมูลการเริ่มต้นกับการพึ่งพา - อินพุตหนึ่งเป็นที่สนใจของผู้ใช้งานของคลาส ( ขนาด ), อีกอันคือการพึ่งพาภายในที่ทำหน้าที่สร้างความสับสนให้ผู้ใช้เท่านั้น detail ( วิดเจ็ต )

พารามิเตอร์ขนาดไม่ใช่การพึ่งพา - เป็นค่าเริ่มต้นต่ออินสแตนซ์ที่ง่าย IoC มีความน่าเชื่อถือสำหรับการอ้างอิงภายนอก (เช่นวิดเจ็ต) แต่ไม่ใช่สำหรับการเริ่มต้นสถานะภายใน

ยิ่งไปกว่านั้นถ้าเครื่องมือมีความจำเป็นสำหรับ 2 ใน 4 วิธีในคลาสนี้เท่านั้น ฉันอาจจะเกิดค่าใช้จ่ายในการสร้างอินสแตนซ์สำหรับ Widget ถึงแม้ว่ามันอาจไม่ได้ใช้!

วิธีการประนีประนอม / กระทบยอดนี้

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

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

ฉันจะบรรลุการกำหนดค่าเริ่มต้นที่รับประกันในโลก DI นี้ได้อย่างไรเมื่อการเริ่มต้นเป็นมากกว่าการพึ่งพาจากภายนอกเท่านั้น


อัปเดต: ฉันเพิ่งสังเกตเห็นว่า Unity 2.0 รองรับการจัดเตรียมค่าสำหรับพารามิเตอร์คอนสตรัคเตอร์ (เช่นสถานะ initializers) ในขณะที่ยังคงใช้กลไกปกติสำหรับ IoC ของการพึ่งพาระหว่างการแก้ไข () บางทีคอนเทนเนอร์อื่นอาจรองรับสิ่งนี้ด้วย? นั่นเป็นการแก้ปัญหาทางเทคนิคของการผสมสถานะ init และ DI ในคอนสตรัคเตอร์เดียว แต่มันก็ยังละเมิดการห่อหุ้ม!
shawnT

ฉันได้ยินคุณ ฉันถามคำถามนี้เพราะฉันรู้สึกว่าสิ่งที่ดีสองอย่าง (การรวม & การห่อหุ้ม) มาด้วยค่าใช้จ่ายของอีกสิ่งหนึ่ง BTW ในตัวอย่างของคุณที่มีเพียง 2 ใน 4 วิธีเท่านั้นที่ต้องการ IWidget ซึ่งจะบ่งบอกว่าอีก 2 วิธีอยู่ใน IMHO ส่วนประกอบที่แตกต่างกัน
urig

3

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

แต่ถ้าคุณกำลังสร้างรถยนต์ที่ต้องการวัตถุเครื่องยนต์ชนิดหนึ่งคุณก็ต้องพึ่งพาภายนอก คุณสามารถสร้างอินสแตนซ์ของเอ็นจิ้นนั้น - เช่น GMOverHeadCamEngine () ใหม่ - ภายในตัวสร้างของวัตถุรถยนต์รักษา encapsulation แต่สร้าง coupling ที่ร้ายกาจมากขึ้นกับ GMOverHeadCamEngine หรือคุณสามารถฉีดได้ agnostically (และมากขึ้นอย่างมีประสิทธิภาพ) บนตัวอย่างเช่นอินเทอร์เฟซ IEngine โดยไม่ต้องพึ่งพาคอนกรีต ไม่ว่าคุณจะใช้คอนเทนเนอร์ IOC หรือ DI แบบง่าย ๆ เพื่อให้ได้สิ่งนี้ไม่ใช่ประเด็น - ประเด็นคือคุณมีรถที่สามารถใช้เครื่องยนต์ได้หลายชนิดโดยไม่ต้องเชื่อมต่อกับคู่ของพวกเขาจึงทำให้ codebase ของคุณยืดหยุ่นและ ผลข้างเคียงน้อยกว่า

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


3

ขึ้นอยู่กับว่าการพึ่งพานั้นเป็นรายละเอียดการนำไปปฏิบัติจริง ๆ หรือสิ่งที่ลูกค้าต้องการ / จำเป็นต้องรู้ในบางวิธีหรืออย่างอื่น สิ่งหนึ่งที่เกี่ยวข้องคือระดับของนามธรรมที่เป็นเป้าหมายระดับ นี่คือตัวอย่างบางส่วน:

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

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

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


2

ฉันเชื่อในความเรียบง่าย การใช้ IOC / Dependecy Injection ในโดเมน Domain ไม่ได้ทำการปรับปรุงใด ๆ ยกเว้นการทำให้โค้ดยากขึ้นไปยัง main โดยการมีไฟล์ xml ภายนอกที่อธิบายความสัมพันธ์ เทคโนโลยีหลายอย่างเช่น EJB 1.0 / 2.0 & struts 1.1 กลับด้านโดยการลดสิ่งที่ใส่ไว้ใน XML และลองใส่ในโค้ดเป็นคำอธิบายประกอบ ฯลฯ ดังนั้นการใช้ IOC สำหรับทุกชั้นเรียนที่คุณพัฒนาจะทำให้โค้ดไม่สมเหตุสมผล

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

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


2

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

ตอนที่ฉันขับรถ Kombi รุ่นเก่าของฉันปี 1971 ฉันสามารถกดคันเร่งได้เร็วขึ้น ฉันไม่จำเป็นต้องรู้ว่าทำไม แต่คนที่สร้าง Kombi ที่โรงงานรู้ว่าทำไม

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

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

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

ณ รันไทม์เซอร์วิสเซอร์วิสที่สร้างขึ้นเรียกว่า apon เพื่อทำงานจริงบางอย่าง ในขณะนี้ผู้เรียกใช้ออบเจ็กต์ไม่ทราบรายละเอียดของการใช้งาน

ฉันกำลังขับรถ Kombi ของฉันไปที่ชายหาด

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

ฉันสามารถขับรถคันใหม่ของฉันไปที่ชายหาดได้เช่นกัน การห่อหุ้มไม่แตก

หากการเปลี่ยนแปลงรายละเอียดการใช้งานภาชนะ DI (หรือโรงงาน) ไม่จำเป็นต้องเปลี่ยน คุณไม่เคยพยายามซ่อนรายละเอียดการใช้งานจากโรงงานตั้งแต่แรก


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

2

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

กรณีที่สาเหตุที่ DI ละเมิด encapsulation นั้นชัดเจนเมื่อส่วนประกอบที่คุณกำลังดำเนินการถูกส่งไปยังปาร์ตี้ "ภายนอก" (คิดว่าจะเขียนห้องสมุดสำหรับลูกค้า)

เมื่อส่วนประกอบของฉันต้องการให้ส่วนประกอบย่อยถูกฉีดผ่านทางคอนสตรัคเตอร์ (หรือคุณสมบัติสาธารณะ) ไม่มีการรับประกันใด ๆ

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

ในขณะเดียวกันก็ไม่สามารถพูดได้ว่า

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

คำพูดที่ทั้งสองจะมาจากวิกิพีเดีย

เพื่อให้ตัวอย่างที่เฉพาะเจาะจง: ฉันต้องส่ง DLL ฝั่งไคลเอ็นต์ที่ลดความซับซ้อนและซ่อนการสื่อสารไปยังบริการ WCF (เป็นซุ้มระยะไกล) เพราะมันขึ้นอยู่กับคลาสพร็อกซี WCF ที่แตกต่างกัน 3 คลาสหากฉันใช้วิธี DI ฉันถูกบังคับให้เปิดเผยผ่านตัวสร้าง เมื่อถึงตอนนั้นฉันก็เผยให้เห็นภายในของเลเยอร์การสื่อสารซึ่งฉันพยายามซ่อน

โดยทั่วไปฉันทั้งหมดสำหรับ DI ในตัวอย่างนี้ (สุดขีด) มันทำให้ฉันรู้สึกอันตราย


2

DI ละเมิด Encapsulation สำหรับวัตถุที่ไม่แชร์ - ระยะเวลา วัตถุที่ใช้ร่วมกันมีอายุขัยนอกวัตถุที่ถูกสร้างขึ้นและดังนั้นจะต้อง AGGREGATED เป็นวัตถุที่ถูกสร้างขึ้น วัตถุที่เป็นส่วนตัวของวัตถุที่กำลังสร้างควรถูกรวมเข้าไปในวัตถุที่สร้างขึ้น - เมื่อวัตถุที่สร้างขึ้นถูกทำลายมันจะนำวัตถุที่ประกอบขึ้นมาด้วย ลองยกตัวอย่างร่างกายมนุษย์ สิ่งที่สงบและสิ่งที่รวม ถ้าเราใช้ DI ผู้สร้างร่างกายมนุษย์จะมีวัตถุ 100 ชิ้น ยกตัวอย่างเช่นอวัยวะหลายส่วนสามารถทดแทนได้ แต่พวกเขายังคงประกอบเข้าสู่ร่างกาย เซลล์เม็ดเลือดถูกสร้างขึ้นในร่างกาย (และถูกทำลาย) ทุกวันโดยไม่จำเป็นต้องมีอิทธิพลภายนอก (นอกเหนือจากโปรตีน) ดังนั้นเซลล์เม็ดเลือดจะถูกสร้างขึ้นภายในโดยร่างกาย - BloodCell ใหม่ ()

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


1

ฉันต่อสู้กับความคิดนี้เช่นกัน ในตอนแรกความต้องการในการใช้ภาชนะ DI (เช่นฤดูใบไม้ผลิ) เพื่อยกตัวอย่างวัตถุที่รู้สึกเหมือนการกระโดดผ่านห่วง แต่ในความเป็นจริงแล้วมันไม่ได้เป็นห่วง - เป็นอีกวิธีหนึ่งที่ 'เผยแพร่' เพื่อสร้างวัตถุที่ฉันต้องการ แน่นอนว่าการห่อหุ้มคือ 'เสีย' เพราะบางคน 'นอกห้องเรียน' รู้ว่ามันต้องการอะไร แต่จริงๆแล้วมันไม่ใช่ระบบที่เหลือที่รู้ว่ามันคือ DI container ไม่มีอะไรที่วิเศษเกิดขึ้นแตกต่างกันเพราะ DI 'รู้' วัตถุหนึ่งต้องการอีกอย่างหนึ่ง

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


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

1

PS โดยการให้การพึ่งพาการฉีดที่คุณไม่จำเป็นต้องทำลายEncapsulation ตัวอย่าง:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

รหัสลูกค้ายังไม่ทราบรายละเอียดการใช้งาน


ในตัวอย่างของคุณเป็นอย่างไรฉีด? (ยกเว้นการตั้งชื่อฟังก์ชั่น setter ของคุณ)
foo

1

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

นอกจากนี้ด้วยการใช้คุณสมบัติการจัดเรียงอัตโนมัติ (เช่น Autofac สำหรับ C # เป็นต้น) ทำให้รหัสนั้นสะอาดเป็นพิเศษ ด้วยการสร้างวิธีการขยายสำหรับตัวสร้าง Autofac ฉันสามารถตัดรหัสการกำหนดค่า DI จำนวนมากที่ฉันจะต้องรักษาไว้ตลอดเวลาเมื่อรายการการขึ้นต่อกันเพิ่มขึ้น


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

1

ฉันคิดว่าตัวเองเห็นได้ชัดว่าอย่างน้อย DI อย่างมากทำให้การห่อหุ้มอ่อนแอลงอย่างมีนัยสำคัญ นอกจากนี้ที่นี่มีข้อเสียอื่น ๆ ของ DI ที่ต้องพิจารณา

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

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

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

เมื่อคำนึงถึงสิ่งนั้นฉันคิดว่าคำถามที่สำคัญที่สุดจะกลายเป็น " การอ้างอิงที่เฉพาะเจาะจงจำเป็นต้องระบุไว้ภายนอกหรือไม่ " ในทางปฏิบัติฉันไม่ค่อยพบว่าจำเป็นต้องสร้างการพึ่งพาจากภายนอกเพียงเพื่อสนับสนุนการทดสอบ

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

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

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

ดังนั้นคำตอบของฉันคือคำถามนี้ ใช้ DI ซึ่งคุณสามารถระบุปัญหาจริงที่แก้ไขให้คุณซึ่งคุณไม่สามารถแก้ปัญหาอื่น ๆ ได้ง่ายขึ้น


0

ฉันยอมรับว่าถูกนำไปใช้อย่างรุนแรง DI สามารถละเมิด encapsulation DI มักจะแสดงการพึ่งพาซึ่งไม่เคยถูกห่อหุ้มอย่างแท้จริง นี่คือตัวอย่างง่าย ๆ ที่ยืมมาจากSingletonsของMiško Hevery คือ Liars ทางพยาธิวิทยา :

คุณเริ่มต้นด้วยการทดสอบ CreditCard และเขียนการทดสอบหน่วยง่ายๆ

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

เดือนถัดไปคุณจะได้รับบิล 100 เหรียญ ทำไมคุณถึงถูกเรียกเก็บเงิน? การทดสอบหน่วยส่งผลกระทบต่อฐานข้อมูลการผลิต Database.getInstance()ภายในสายบัตรเครดิต Refactoring CreditCard เพื่อให้มันใช้เวลาDatabaseInterfaceในตัวสร้างของมันเปิดเผยความจริงที่ว่ามีการพึ่งพา แต่ฉันจะยืนยันว่าการพึ่งพาไม่เคยถูกห่อหุ้มด้วยการเริ่มต้นเนื่องจากชั้น CreditCard ทำให้เกิดผลข้างเคียงที่มองเห็นจากภายนอก หากคุณต้องการทดสอบ CreditCard โดยไม่ต้องทำการเปลี่ยนใหม่คุณสามารถสังเกตการพึ่งพาได้อย่างแน่นอน

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

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


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

1
จุด DI คือการหลีกเลี่ยงการเปลี่ยนแปลงที่คุณอธิบาย หากคุณเปลี่ยนจาก Web API เป็น PayPal คุณจะไม่เปลี่ยนการทดสอบส่วนใหญ่เพราะพวกเขาจะใช้ MockPaymentService และ CreditCard จะได้รับการสร้างขึ้นด้วย PaymentService คุณมีการทดสอบเพียงไม่กี่ครั้งที่ดูการโต้ตอบที่แท้จริงระหว่าง CreditCard และ PaymentService จริงดังนั้นการเปลี่ยนแปลงในอนาคตจะถูกแยกออกมาก ผลประโยชน์จะยิ่งใหญ่กว่าสำหรับกราฟการพึ่งพาที่ลึกซึ้งยิ่งขึ้น (เช่นคลาสที่ขึ้นอยู่กับ CreditCard)
Craig P. Motlin

@Craig p. Motlin CreditCardวัตถุนั้นสามารถเปลี่ยนจาก WebAPI เป็น PayPal ได้อย่างไรโดยไม่ต้องเปลี่ยนอะไรจากภายนอกในชั้นเรียน?
เอียนบอยด์

@Ian ฉันกล่าวถึง CreditCard ควร refactored เพื่อใช้ DatabaseInterface ในนวกรรมิกซึ่งป้องกันจากการเปลี่ยนแปลงในการใช้งานของ DatabaseInterfaces บางทีนั่นอาจจะต้องเป็นแบบทั่วไปมากขึ้นและใช้ StorageInterface ซึ่ง WebAPI อาจเป็นการนำไปใช้อีกครั้ง PayPal อยู่ในระดับที่ไม่ถูกต้องเนื่องจากเป็นอีกทางเลือกหนึ่งสำหรับ CreditCard ทั้ง PayPal และ CreditCard สามารถใช้ PaymentInterface เพื่อป้องกันส่วนอื่น ๆ ของแอปพลิเคชันจากภายนอกตัวอย่างนี้
Craig P. Motlin

@ kizzx2 กล่าวอีกอย่างหนึ่งก็คือไร้สาระที่จะบอกว่าบัตรเครดิตควรใช้ PayPal พวกเขาเป็นทางเลือกในชีวิตจริง
Craig P. Motlin

0

ฉันคิดว่ามันเป็นเรื่องของขอบเขต เมื่อคุณกำหนดการห่อหุ้ม (ไม่แจ้งให้ทราบ) คุณต้องกำหนดฟังก์ชันการทำงานที่ห่อหุ้ม

  1. Class as is : encapsulating คือความรับผิดชอบของชั้นเรียนเท่านั้น สิ่งที่รู้วิธีการทำ ตามตัวอย่างการเรียงลำดับ หากคุณฉีดตัวเปรียบเทียบเพื่อสั่งซื้อสมมติว่าลูกค้านั่นไม่ใช่ส่วนหนึ่งของสิ่งที่ห่อหุ้ม: quicksort

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

เมื่อคุณเป็นคลาสโปรแกรมมิงนั้นคือการใช้ความรับผิดชอบเดี่ยวเป็นคลาสคุณกำลังใช้อ็อพชัน 1

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

นี่คือการใช้งานอินสแตนซ์ที่กำหนดค่าไว้:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

นี่คือวิธีที่รหัสลูกค้าอื่นใช้:

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

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


0

อาจเป็นมูลค่าการกล่าวขวัญที่Encapsulationค่อนข้างขึ้นอยู่กับมุมมอง

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

จากมุมมองของคนที่ทำงานในAชั้นเรียนในตัวอย่างที่สอง Aรู้น้อยมากเกี่ยวกับธรรมชาติของthis.b

ในขณะที่ไม่มี DI

new A()

VS

new A(new B())

ผู้ที่ดูรหัสนี้รู้เพิ่มเติมเกี่ยวกับธรรมชาติของAในตัวอย่างที่สอง

ด้วย DI อย่างน้อยความรู้ที่รั่วไหลทั้งหมดอยู่ในที่เดียว

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