ตั้งแต่การเรียนรู้การทดสอบอัตโนมัติ (และความรัก) ฉันพบว่าตัวเองใช้รูปแบบการฉีดพึ่งพาในเกือบทุกโครงการ มันเหมาะสมหรือไม่ที่จะใช้รูปแบบนี้เมื่อทำงานกับการทดสอบอัตโนมัติ? มีสถานการณ์ใดบ้างที่คุณควรหลีกเลี่ยงการใช้การฉีดพึ่งพา
ตั้งแต่การเรียนรู้การทดสอบอัตโนมัติ (และความรัก) ฉันพบว่าตัวเองใช้รูปแบบการฉีดพึ่งพาในเกือบทุกโครงการ มันเหมาะสมหรือไม่ที่จะใช้รูปแบบนี้เมื่อทำงานกับการทดสอบอัตโนมัติ? มีสถานการณ์ใดบ้างที่คุณควรหลีกเลี่ยงการใช้การฉีดพึ่งพา
คำตอบ:
โดยทั่วไปการขึ้นต่อกันทำให้สมมติฐานบางอย่าง (ปกติ แต่ไม่ถูกต้องเสมอไป) เกี่ยวกับธรรมชาติของวัตถุของคุณ หากสิ่งเหล่านี้ผิด DI อาจไม่ใช่ทางออกที่ดีที่สุด:
ครั้งแรกส่วนใหญ่โดยทั่วไปDI อนุมานว่าการมีเพศสัมพันธ์แน่นของการใช้วัตถุที่ไม่ดีเสมอ นี่คือสาระสำคัญของหลักการการผกผันของการพึ่งพา: "การพึ่งพาไม่ควรทำเมื่อมีการสรุป; เพียง แต่ในสิ่งที่เป็นนามธรรม"
สิ่งนี้จะปิดวัตถุที่ต้องพึ่งพาเพื่อเปลี่ยนแปลงโดยขึ้นอยู่กับการเปลี่ยนแปลงในการนำไปปฏิบัติ คลาสขึ้นอยู่กับ ConsoleWriter โดยเฉพาะจะต้องเปลี่ยนถ้าเอาท์พุทต้องไปที่ไฟล์แทน แต่ถ้าคลาสนั้นขึ้นอยู่กับ IWriter เปิดเผยวิธีการเขียน () เราสามารถแทนที่ ConsoleWriter ปัจจุบันที่ใช้กับ FileWriter และของเรา ชั้นเรียนพึ่งพาจะไม่ทราบความแตกต่าง (หลักการทดแทน Liskhov)
อย่างไรก็ตามการออกแบบไม่สามารถปิดการเปลี่ยนแปลงได้ทุกประเภท หากการออกแบบของอินเทอร์เฟซ IWriter เปลี่ยนแปลงตัวเองเพื่อเพิ่มพารามิเตอร์ในการเขียน () วัตถุรหัสพิเศษ (อินเทอร์เฟซ IWriter) จะต้องเปลี่ยนตอนนี้บนวัตถุ / วิธีการใช้งานและการใช้งาน (s) หากการเปลี่ยนแปลงในอินเทอร์เฟซที่เกิดขึ้นจริงมีแนวโน้มมากกว่าการเปลี่ยนแปลงในการใช้งานอินเทอร์เฟซดังกล่าวการมีเพศสัมพันธ์แบบหลวม ๆ
ประการที่สองและข้อพิสูจน์DI สันนิษฐานว่าระดับขึ้นจะไม่เป็นสถานที่ที่ดีในการสร้างการพึ่งพา สิ่งนี้นำไปสู่หลักการความรับผิดชอบเดี่ยว; หากคุณมีรหัสที่สร้างการพึ่งพาและใช้มันก็มีเหตุผลสองประการที่คลาสที่ต้องพึ่งพาอาจต้องเปลี่ยน (การเปลี่ยนแปลงการใช้งานหรือการใช้งาน) ซึ่งเป็นการละเมิด SRP
อย่างไรก็ตามอีกครั้งการเพิ่มเลเยอร์ทางอ้อมสำหรับ DI อาจเป็นวิธีแก้ปัญหาที่ไม่มีอยู่ ถ้ามันมีเหตุผลที่จะแค็ปซูลลอจิกในการพึ่งพา แต่เหตุผลนั้นเป็นเพียงการดำเนินการของการพึ่งพานั้นมันเป็นความเจ็บปวดมากกว่าที่จะเขียนรหัสการแก้ปัญหาแบบคู่ - อิสระของการพึ่งพา (การฉีดสถานที่ตั้งโรงงาน) ที่จะใช้new
และลืมมัน
สุดท้ายDI โดยธรรมชาติของมันรวมศูนย์ความรู้การอ้างอิงและการใช้งานของพวกเขา สิ่งนี้จะเพิ่มจำนวนของการอ้างอิงที่แอสเซมบลีที่ดำเนินการฉีดต้องมีและในกรณีส่วนใหญ่ไม่ลดจำนวนของการอ้างอิงที่จำเป็นต้องใช้โดยแอสเซมบลีของคลาสที่ขึ้นอยู่กับที่เกิดขึ้นจริง
บางสิ่งบางอย่างต้องมีความรู้เกี่ยวกับการพึ่งพาอินเทอร์เฟซการพึ่งพาและการใช้งานการพึ่งพาเพื่อ "เชื่อมต่อจุด" และตอบสนองการพึ่งพานั้น DI มีแนวโน้มที่จะวางความรู้ทั้งหมดในระดับที่สูงมากไม่ว่าจะเป็นในคอนเทนเนอร์ IoC หรือในรหัสที่สร้างวัตถุ "หลัก" เช่นแบบฟอร์มหลักหรือตัวควบคุมที่ต้องให้ความชุ่มชื้น (หรือจัดให้มีวิธีการจากโรงงานสำหรับ) การพึ่งพา สิ่งนี้สามารถใส่รหัสคู่ที่แน่นหนาและการอ้างอิงแอสเซมบลีจำนวนมากในแอปของคุณซึ่งจำเป็นต้องมีความรู้นี้เพื่อ "ซ่อน" จากคลาสที่ขึ้นกับความเป็นจริง (ซึ่งจากมุมมองพื้นฐานที่สำคัญคือ สถานที่ที่ดีที่สุดที่จะมีความรู้นี้ใช้ที่ไหน)
โดยปกติแล้วจะไม่ลบการอ้างอิงดังกล่าวจากการลดลงในรหัส; การพึ่งพาต้องยังคงอ้างอิงไลบรารีที่มีอินเทอร์เฟซสำหรับการอ้างอิงซึ่งอยู่ในหนึ่งในสามแห่ง:
ทั้งหมดนี้อีกครั้งเพื่อแก้ปัญหาในสถานที่ที่อาจไม่มี
นอกกรอบการพึ่งพาการฉีดการพึ่งพา (ผ่านการฉีดคอนสตรัคเตอร์หรือการตั้งค่าการฉีด) เป็นเกมที่เกือบจะเป็นศูนย์รวม: คุณลดการมีเพศสัมพันธ์ระหว่างวัตถุ A และการพึ่งพา B แต่ตอนนี้วัตถุใด ๆ ที่ต้องการอินสแตนซ์ของ A ยังสร้างวัตถุ B
คุณลดการเชื่อมต่อระหว่าง A และ B ลงเล็กน้อย แต่ลดการห่อหุ้มของ A และเพิ่มการเชื่อมต่อระหว่าง A และคลาสใด ๆ ที่ต้องสร้างอินสแตนซ์ของ A โดยการเชื่อมต่อเข้ากับการพึ่งพาของ A
ดังนั้นการฉีดพึ่งพา (โดยไม่มีกรอบ) เป็นอันตรายอย่างเท่าเทียมกันเกี่ยวกับมันเป็นประโยชน์
ค่าใช้จ่ายเพิ่มเติมมักจะสมเหตุสมผลได้อย่างง่ายดายอย่างไรก็ตาม: หากรหัสลูกค้ารู้เพิ่มเติมเกี่ยวกับวิธีการสร้างการพึ่งพากว่าวัตถุที่ตัวเองทำแล้วการฉีดพึ่งพาจะลดการมีเพศสัมพันธ์จริง ๆ ; ตัวอย่างเช่นเครื่องสแกนไม่ทราบมากเกี่ยวกับวิธีรับหรือสร้างอินพุตสตรีมเพื่อวิเคราะห์อินพุตหรือซอร์สโค้ดที่ไคลเอนต์โค้ดต้องการแยกวิเคราะห์จากดังนั้นการฉีดคอนสตรัคเตอร์ของอินพุตจึงเป็นทางออกที่ชัดเจน
การทดสอบเป็นอีกเหตุผลหนึ่งเพื่อให้สามารถใช้การอ้างอิงแบบจำลอง นั่นควรหมายถึงการเพิ่มนวกรรมิกพิเศษที่ใช้สำหรับการทดสอบเท่านั้นที่อนุญาตให้มีการพึ่งพา: หากคุณเปลี่ยนคอนสตรัคเตอร์ของคุณเพื่อให้ต้องมีการพึ่งพาการฉีดเสมอทันใดนั้นคุณต้องรู้เกี่ยวกับการพึ่งพาของผู้อ้างอิง การพึ่งพาโดยตรงและคุณไม่สามารถทำงานให้สำเร็จได้
มันจะมีประโยชน์ แต่คุณควรถามตัวเองอย่างแน่นอนสำหรับการพึ่งพากันแต่ละครั้งผลประโยชน์จากการทดสอบนั้นคุ้มค่ากับราคาหรือไม่และฉันต้องการจะล้อเลียนการพึ่งพานี้ในขณะทดสอบหรือไม่?
เมื่อมีการเพิ่มเฟรมเวิร์กการฉีดพึ่งพาและการสร้างการพึ่งพาไม่ได้ถูกมอบหมายให้กับรหัสลูกค้า แต่แทนที่จะเป็นเฟรมเวิร์กการวิเคราะห์ต้นทุน / ผลประโยชน์จะเปลี่ยนไปอย่างมาก
ในกรอบการพึ่งพาการฉีดการแลกเปลี่ยนจะแตกต่างกันเล็กน้อย สิ่งที่คุณสูญเสียจากการฉีดการพึ่งพาคือความสามารถในการรู้ได้อย่างง่ายดายว่าการใช้งานแบบใดที่คุณต้องพึ่งพาและเปลี่ยนความรับผิดชอบในการตัดสินใจว่าคุณต้องพึ่งพาการพึ่งพากระบวนการแก้ปัญหาอัตโนมัติบางอย่าง (เช่นหากเราต้องการ @ Inject'ed Foo ต้องมีบางสิ่งที่ @Provides Foo และมีการพึ่งพาการฉีดที่มีอยู่) หรือไฟล์กำหนดค่าระดับสูงบางอย่างที่กำหนดผู้ให้บริการที่ควรใช้สำหรับแต่ละทรัพยากรหรือไฮบริดของทั้งสอง (ตัวอย่างเช่นอาจ เป็นกระบวนการแก้ไขปัญหาอัตโนมัติสำหรับการอ้างอิงที่สามารถแทนที่ได้หากจำเป็นโดยใช้ไฟล์กำหนดค่า)
ในการฉีดคอนสตรัคเตอร์ฉันคิดว่าข้อดีของการทำเช่นนั้นจบลงอีกครั้งคล้ายกับค่าใช้จ่ายในการทำเช่นนั้น: คุณไม่จำเป็นต้องรู้ว่าใครเป็นผู้ให้ข้อมูลที่คุณไว้ใจและถ้ามีศักยภาพหลายอย่าง ผู้ให้บริการคุณไม่จำเป็นต้องรู้คำสั่งซื้อที่ต้องการเพื่อตรวจสอบผู้ให้บริการตรวจสอบให้แน่ใจว่าทุกสถานที่ที่ต้องการตรวจสอบข้อมูลสำหรับผู้ให้บริการที่มีศักยภาพทั้งหมดและอื่น ๆ เพราะสิ่งเหล่านั้นได้รับการจัดการในระดับสูง เวที
ในขณะที่ฉันไม่ได้มีประสบการณ์มากกับ DI framework แต่ความประทับใจของฉันคือพวกเขาให้ประโยชน์มากกว่าราคาเมื่อปวดหัวในการค้นหาผู้ให้บริการข้อมูลหรือบริการที่ถูกต้องที่คุณต้องการมีค่าใช้จ่ายสูงกว่าอาการปวดหัว เมื่อสิ่งที่ล้มเหลวไม่ทราบทันทีว่ารหัสใดที่ให้ข้อมูลที่ไม่ดีซึ่งทำให้รหัสของคุณล้มเหลวในภายหลัง
ในบางกรณีรูปแบบอื่น ๆ ที่ปิดบังการพึ่งพา (เช่นผู้ให้บริการ) ได้รับการรับรองแล้ว (และอาจพิสูจน์คุณค่าของพวกเขา) เมื่อกรอบ DI ปรากฏบนฉากและกรอบ DI ถูกนำมาใช้เพราะพวกเขาเสนอความได้เปรียบในการแข่งขันเช่น รหัสสำเร็จรูปน้อยกว่าหรืออาจลดการปิดกั้นผู้ให้บริการของการพึ่งพาเมื่อจำเป็นต้องกำหนดผู้ให้บริการที่ใช้งานจริง
ถ้าคุณกำลังสร้างเอนทิตีฐานข้อมูลคุณควรมีคลาสโรงงานบางส่วนที่คุณจะแทรกลงในคอนโทรลเลอร์ของคุณแทน
หากคุณต้องการสร้างวัตถุดั้งเดิมเช่น ints หรือ longs นอกจากนี้คุณควรสร้าง "ด้วยมือ" ส่วนใหญ่ของวัตถุไลบรารีมาตรฐานเช่นวันที่ guids ฯลฯ
หากคุณต้องการฉีดสตริงการกำหนดค่าอาจเป็นความคิดที่ดีกว่าในการแทรกออบเจ็กต์การกำหนดค่าบางอย่าง (โดยทั่วไปขอแนะนำให้ห่อประเภทที่เรียบง่ายเป็นวัตถุที่มีความหมาย: int temperatureInCelsiusDegrees -> อุณหภูมิ CelciusDeegree)
และอย่าใช้ตัวระบุตำแหน่งบริการเป็นทางเลือกในการเพิ่มการพึ่งพามันเป็นแบบป้องกันข้อมูลเพิ่มเติม: http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
เมื่อคุณไม่ได้รับสิ่งใดโดยทำให้โครงการของคุณบำรุงรักษาและทดสอบได้
อย่างจริงจังฉันรัก IoC และ DI โดยทั่วไปและฉันบอกว่า 98% ของเวลาที่ฉันจะใช้รูปแบบนั้นโดยไม่ล้มเหลว มันมีความสำคัญอย่างยิ่งในสภาพแวดล้อมที่มีผู้ใช้หลายคนซึ่งคุณสามารถนำโค้ดกลับมาใช้ซ้ำได้อีกครั้งโดยสมาชิกในทีมและโปรเจ็กต์ที่แตกต่างกันเนื่องจากแยกตรรกะออกจากการใช้งาน การบันทึกเป็นตัวอย่างสำคัญของสิ่งนี้อินเทอร์เฟซ ILog ที่ฉีดเข้าไปในคลาสนั้นสามารถบำรุงรักษาได้มากกว่าพันเท่าเพียงแค่เสียบเข้ากับกรอบการบันทึกของคุณเนื่องจากคุณไม่มีการรับประกันว่าโครงการอื่นจะใช้เฟรมเวิร์กการบันทึกเดียวกัน อย่างใดอย่างหนึ่ง!)
อย่างไรก็ตามมีบางครั้งที่มันไม่ใช่รูปแบบที่ใช้งานได้ ตัวอย่างเช่นจุดเข้าใช้งานที่นำมาใช้ในบริบทแบบคงที่โดยผู้เริ่มต้นที่ไม่ใช่ overridable (WebMethods ฉันกำลังดูคุณ แต่วิธีการหลัก () ของคุณในคลาสโปรแกรมของคุณเป็นอีกตัวอย่างหนึ่ง) ไม่สามารถมีการอ้างอิงที่ initialisation ได้ เวลา. ฉันก็จะบอกว่าต้นแบบหรือรหัสการสืบสวนที่ไม่เป็นที่รู้จักก็เป็นผู้สมัครที่แย่เช่นกัน ประโยชน์ของ DI นั้นค่อนข้างมากในระยะกลางถึงระยะยาว (ทดสอบได้และบำรุงรักษา) ถ้าคุณมั่นใจว่าคุณจะทิ้งส่วนใหญ่ของรหัสภายในหนึ่งสัปดาห์หรือดังนั้นฉันจะบอกว่าคุณไม่ได้อะไรโดยการแยก การพึ่งพาเพียงใช้เวลาที่ปกติแล้วคุณจะใช้การทดสอบและการแยกการขึ้นต่อกันเพื่อให้โค้ดทำงาน
สรุปแล้วก็มีเหตุผลที่จะใช้วิธีการหรือรูปแบบใด ๆ ในทางปฏิบัติเนื่องจากไม่มีสิ่งใดที่สามารถนำไปใช้ได้ 100% ของเวลา
สิ่งหนึ่งที่ควรทราบคือความคิดเห็นของคุณเกี่ยวกับการทดสอบอัตโนมัติ: คำจำกัดความของฉันคือการทดสอบการทำงานอัตโนมัติเช่นการทดสอบซีลีเนียมแบบมีสคริปต์หากคุณอยู่ในบริบทเว็บ โดยทั่วไปการทดสอบเหล่านี้เป็นกล่องดำสนิทโดยไม่จำเป็นต้องรู้เกี่ยวกับการทำงานด้านในของรหัส หากคุณอ้างถึงการทดสอบหน่วยหรือการรวมเข้าด้วยกันฉันบอกว่ารูปแบบ DI นั้นสามารถใช้ได้กับโครงการใด ๆ ที่ต้องอาศัยการทดสอบกล่องสีขาวประเภทนั้นเป็นอย่างมากเนื่องจากมันช่วยให้คุณสามารถทดสอบสิ่งต่าง ๆ เช่น วิธีการที่สัมผัสกับฐานข้อมูลโดยไม่จำเป็นต้องมีฐานข้อมูลอยู่
ในขณะที่คำตอบอื่น ๆ มุ่งเน้นด้านเทคนิคฉันต้องการเพิ่มมิติการปฏิบัติ
ในช่วงหลายปีที่ผ่านมาฉันได้ข้อสรุปว่ามีข้อกำหนดที่จำเป็นหลายประการที่ต้องปฏิบัติหากการแนะนำการฉีดเข้าสู่ระบบนั้นประสบความสำเร็จ
ควรมีเหตุผลที่จะแนะนำ
ฟังดูชัดเจน แต่ถ้าโค้ดของคุณได้รับสิ่งต่าง ๆ จากฐานข้อมูลและส่งคืนโดยไม่มีตรรกะให้เพิ่มคอนเทนเนอร์ DI ทำให้สิ่งต่าง ๆ ซับซ้อนขึ้นโดยไม่มีประโยชน์จริง การทดสอบการรวมจะมีความสำคัญมากกว่าที่นี่
ทีมจะต้องได้รับการฝึกฝนและขึ้นบนเรือ
เว้นแต่ว่าทีมส่วนใหญ่อยู่บนเรือและเข้าใจ DI การเพิ่มการผกผันของคอนเทนเนอร์ควบคุมกลายเป็นอีกวิธีหนึ่งในการทำสิ่งต่าง ๆ และทำให้รหัสฐานซับซ้อนยิ่งขึ้น
ถ้า DI ถูกแนะนำโดยสมาชิกใหม่ของทีมเพราะพวกเขาเข้าใจและชอบและเพียงต้องการแสดงให้เห็นว่าพวกเขาดีและทีมไม่ได้มีส่วนร่วมอย่างจริงจังมีความเสี่ยงจริงที่จะลดคุณภาพของ รหัส.
คุณต้องทดสอบ
ในขณะที่ decoupling เป็นสิ่งที่ดี DI สามารถย้ายความละเอียดของการพึ่งพาจากเวลารวบรวมเพื่อรันเวลา นี่มันค่อนข้างอันตรายถ้าคุณทดสอบไม่ดี ความล้มเหลวในการแก้ไขเวลาทำงานอาจมีค่าใช้จ่ายสูงในการติดตามและแก้ไข
(ชัดเจนจากการทดสอบของคุณที่คุณทำ แต่หลายทีมไม่ทดสอบตามขอบเขตที่ DI กำหนด)
นี่ไม่ใช่คำตอบที่สมบูรณ์ แต่เป็นอีกประเด็นหนึ่ง
เมื่อคุณมีแอปพลิเคชั่นที่เริ่มต้นครั้งเดียวการรันเป็นเวลานาน (เช่นเว็บแอป) DI อาจดี
เมื่อคุณมีแอปพลิเคชั่นที่เริ่มทำงานหลายครั้งและทำงานในเวลาที่สั้นลง (เช่นแอพมือถือ) คุณอาจไม่ต้องการคอนเทนเนอร์
ลองใช้หลักการ OOP ขั้นพื้นฐาน: ใช้การสืบทอดเพื่อแยกการทำงานทั่วไป, แค็ปซูล (ซ่อน) สิ่งต่าง ๆ ที่ควรได้รับการปกป้องจากโลกภายนอกโดยใช้สมาชิก / ประเภทส่วนตัว / ภายใน / ที่มีการป้องกัน ใช้ทดสอบกรอบที่มีประสิทธิภาพใด ๆ ที่จะฉีดรหัสสำหรับการทดสอบเท่านั้นตัวอย่างเช่นhttps://www.typemock.com/หรือhttps://www.telerik.com/products/mocking.aspx
จากนั้นลองเขียนใหม่ด้วย DI และเปรียบเทียบโค้ดสิ่งที่คุณมักจะเห็นด้วย DI:
ฉันจะพูดจากสิ่งที่ฉันเห็นเกือบตลอดเวลาคุณภาพของรหัสจะลดลงเมื่อ DI
อย่างไรก็ตามหากคุณใช้ตัวดัดแปลงการเข้าถึง "สาธารณะ" เท่านั้นในการประกาศคลาสและ / หรือตัวดัดแปลงสาธารณะ / ส่วนตัวสำหรับสมาชิกและ / หรือคุณไม่มีทางเลือกที่จะซื้อเครื่องมือทดสอบที่มีราคาแพงและในเวลาเดียวกันคุณต้องทดสอบหน่วยที่สามารถ ' ไม่ถูกแทนที่ด้วยการทดสอบเชิงบูรณาการและ / หรือคุณมีอินเทอร์เฟซสำหรับชั้นเรียนที่คุณคิดว่าจะฉีด DI เป็นตัวเลือกที่ดี!
ป.ล. บางทีฉันจะได้รับ minuses จำนวนมากสำหรับโพสต์นี้ฉันเชื่อว่าเพราะนักพัฒนาสมัยใหม่ส่วนใหญ่ไม่เข้าใจว่าทำไมและทำไมต้องใช้คำหลักภายในและวิธีการลดการเชื่อมต่อของคอมโพเนนต์ของคุณ ลองโค้ดและเปรียบเทียบ
ทางเลือกที่จะพึ่งพาการฉีดใช้บริการส ตัวระบุตำแหน่งบริการเข้าใจได้ง่ายขึ้นตรวจแก้จุดบกพร่องและทำให้การสร้างวัตถุง่ายขึ้นโดยเฉพาะถ้าคุณไม่ได้ใช้กรอบงาน DI Service Locators เป็นรูปแบบที่ดีสำหรับการจัดการการพึ่งพาแบบสแตติกภายนอกเช่นฐานข้อมูลที่คุณจะต้องผ่านเข้าไปในทุกวัตถุในชั้นการเข้าถึงข้อมูลของคุณ
เมื่อrefactoring code legacyมักจะทำให้ refactor ไปยังตัวระบุบริการได้ง่ายกว่าการฉีด Dependency สิ่งที่คุณทำคือการแทนที่อินสแตนซ์ด้วยการค้นหาบริการแล้วปลอมบริการในการทดสอบหน่วยของคุณ
อย่างไรก็ตามมีข้อเสียบางประการสำหรับผู้ให้บริการ การรู้ถึงความแตกต่างของคลาสนั้นยากกว่าเพราะการพึ่งพานั้นซ่อนอยู่ในการใช้งานของคลาสไม่ใช่ในคอนสตรัคเตอร์หรือเซ็ตเตอร์ และการสร้างวัตถุสองชิ้นที่พึ่งพาการใช้งานที่แตกต่างกันของบริการเดียวกันนั้นเป็นเรื่องยากหรือเป็นไปไม่ได้
TLDR : ถ้าคลาสของคุณมีการพึ่งพาแบบคงที่หรือคุณกำลังสร้างรหัสดั้งเดิมผู้ให้บริการจะดีกว่า DI