เหตุใดฉันจึงควรใช้การฉีดพึ่งพา


95

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

ทำไมฉันต้องทำสิ่งต่อไปนี้?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

แทนที่จะเป็นดังต่อไปนี้?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}

8
คุณกำลังแนะนำการพึ่งพาฮาร์ดโค้ดเพื่อ deactivateProfile () (ซึ่งไม่ดี) คุณมีโค้ดที่แยกได้มากขึ้นในรหัสแรกซึ่งทำให้ง่ายต่อการเปลี่ยนแปลงและทดสอบ
Aulis Ronkainen

3
ทำไมคุณจะทำคนแรก? คุณกำลังผ่านการตั้งค่าแล้วไม่สนใจค่าของมัน
Phil N DeBlanc

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

13
@PhilNDeBlanc: รหัสนี้มีความชัดเจนอย่างชัดเจนและไม่ได้บ่งบอกถึงตรรกะของโลกแห่งความเป็นจริง อย่างไรก็ตามdeactivateProfileแนะนำให้ฉันว่าการตั้งค่าเป็นisActiveเท็จโดยไม่สนใจสถานะก่อนหน้านี้เป็นวิธีที่ถูกต้องที่นี่ การเรียกใช้เมธอดโดยเนื้อแท้หมายความว่าคุณต้องการตั้งค่าเป็นไม่แอ็คทีฟไม่ได้รับสถานะแอ็คทีฟปัจจุบัน (เป็น)
Flater

2
รหัสของคุณไม่ใช่ตัวอย่างของการฉีดหรือการผกผัน มันเป็นตัวอย่างของการทำให้เป็นพารามิเตอร์ (ซึ่งมักจะดีกว่า DI)
jpmc26

คำตอบ:


104

ข้อดีคือไม่ต้องมีการฉีดพึ่งพาระดับโปรไฟล์ของคุณ

  • จำเป็นต้องรู้วิธีการสร้างวัตถุการตั้งค่า (ละเมิดหลักการความรับผิดชอบเดี่ยว)
  • สร้างวัตถุการตั้งค่าเสมอด้วยวิธีเดียวกัน (สร้างคลัปแน่นระหว่างสอง)

แต่ด้วยการพึ่งพาการฉีด

  • ตรรกะสำหรับการสร้างวัตถุการตั้งค่าเป็นที่อื่น
  • มันใช้งานวัตถุการตั้งค่าชนิดต่าง ๆ ได้ง่าย

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


23
This may seem (or even be) irrelevant in this particular caseฉันคิดว่ามันมีความเกี่ยวข้องอย่างมากในความเป็นจริง วิธีที่คุณจะได้รับการตั้งค่าหรือไม่ ระบบจำนวนมากที่ฉันเคยเห็นจะมีชุดการตั้งค่าเริ่มต้นที่กำหนดค่าตายตัวและการกำหนดค่าสาธารณะซึ่งคุณต้องโหลดทั้งสองและเขียนทับค่าบางค่าด้วยการตั้งค่าสาธารณะ คุณอาจต้องใช้แหล่งที่มาหลายค่าเริ่มต้น บางทีคุณอาจจะได้รับบางส่วนจากดิสก์อื่น ๆ จากฐานข้อมูล ดังนั้นตรรกะทั้งหมดสำหรับแม้แต่การตั้งค่าสามารถทำได้และบ่อยครั้งก็คือไม่สำคัญ - ไม่ใช่สิ่งที่ต้องใช้รหัสหรือควรสนใจ
VLAZ

นอกจากนี้เรายังสามารถพูดถึงว่าการเริ่มต้นวัตถุสำหรับองค์ประกอบที่ไม่สำคัญเช่นเว็บเซอร์วิสจะ$setting = new Setting();ไม่มีประสิทธิภาพอย่างน่ากลัว การเริ่มต้นการฉีดและวัตถุเกิดขึ้นเพียงครั้งเดียว
vikingsteve

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

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

1
@ user986730 - เป็นจุดที่ดีมากและเน้นว่าหลักการที่แท้จริงที่นี่คือ Dependency Inversionซึ่งการฉีดเป็นเพียงหนึ่งเทคนิคดังนั้นตัวอย่างเช่นใน C คุณไม่สามารถฉีดพึ่งพาโดยใช้ไลบรารี IOC แต่คุณสามารถ ย้อนกลับในเวลารวบรวมโดยรวมถึงไฟล์ต้นฉบับที่แตกต่างกันสำหรับ mocks ของคุณ ฯลฯ ซึ่งมีผลเหมือนกัน
Stephen Byrne

64

การฉีดพึ่งพาทำให้รหัสของคุณง่ายต่อการทดสอบ

ฉันเรียนรู้สิ่งนี้โดยตรงเมื่อฉันได้รับมอบหมายให้แก้ไขข้อผิดพลาดที่จับยากในการรวม PayPal ของ Magento

ปัญหาจะเกิดขึ้นเมื่อ PayPal บอก Magento เกี่ยวกับการชำระเงินที่ล้มเหลว: Magento จะไม่ลงทะเบียนความล้มเหลวอย่างเหมาะสม

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

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

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

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

หากคุณอยากรู้เกี่ยวกับการแก้ไขปัญหานี้ลองดู GitHub repo ได้ที่นี่: https://github.com/bubbleupdev/BUCorefix_Paypalstatus


4
การฉีดพึ่งพาทำให้การทดสอบโค้ดง่ายกว่าโค้ดที่มีการพึ่งพาฮาร์ดโค้ด กำจัดการพึ่งพาจากตรรกะทางธุรกิจโดยสิ้นเชิงจะดียิ่งขึ้น
Ant P

1
และเป็นหนึ่งในวิธีที่สำคัญในการทำในสิ่งที่แสดงให้เห็น @AntP ผ่านparameterization ตรรกะในการแปลงผลลัพธ์ที่กลับมาจากฐานข้อมูลเป็นวัตถุที่ใช้ในการกรอกเทมเพลตของหน้าเว็บ (ที่รู้จักกันทั่วไปว่าเป็น "แบบจำลอง") ไม่ควรทราบว่ามีการดึงข้อมูลเกิดขึ้น มันแค่ต้องการให้วัตถุเหล่านั้นเป็นอินพุต
jpmc26

4
@ jpmc26 แน่นอน - ฉันมักจะพิถีพิถันเกี่ยวกับcore ที่ใช้งานได้, เชลล์ที่จำเป็นซึ่งจริงๆแล้วเป็นเพียงชื่อแฟนซีสำหรับการส่งผ่านข้อมูลไปยังโดเมนของคุณแทนที่จะฉีดการพึ่งพาเข้าไป โดเมนบริสุทธิ์สามารถเป็นหน่วยทดสอบกับไม่มี mocks แล้วถูกห่อในเปลือกที่จะปรับสิ่งต่างๆเช่นการติดตา, ข้อความ ฯลฯ
มด P

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

26

ทำไม (มีปัญหาอะไร)

เหตุใดฉันจึงควรใช้การฉีดพึ่งพา

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

ตัวอย่างเช่นสมมติว่าคุณกำลังเขียนวิดีโอเกมรถแข่ง คุณเริ่มต้นด้วยชั้นเรียนGameซึ่งจะสร้างRaceTrackซึ่งจะสร้าง 8 ซึ่งแต่ละสร้างCars Motorตอนนี้ถ้าคุณต้องการที่ 4 เพิ่มเติมCarsด้วยการเร่งความเร็วที่แตกต่างกันคุณจะต้องเปลี่ยนทุกชั้นกล่าวถึงGameอาจจะยกเว้น

รหัสทำความสะอาด

นี่เป็นเพียงสถาปัตยกรรม / รหัสที่สะอาดขึ้น

ใช่แล้ว

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

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

ตอนนี้สามารถเพิ่มรถ 4 คันที่แตกต่างกันได้โดยเพิ่มบรรทัดเหล่านี้:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • ไม่มีการเปลี่ยนแปลงในRaceTrack, Game, CarหรือMotorมีความจำเป็น - ซึ่งหมายความว่าเราสามารถเป็น 100% แน่ใจว่าเราไม่ได้แนะนำข้อบกพร่องใหม่ ๆ ที่นั่น!
  • แทนที่จะต้องข้ามไปมาระหว่างหลาย ๆ ไฟล์คุณจะสามารถเห็นการเปลี่ยนแปลงทั้งหมดบนหน้าจอได้ในเวลาเดียวกัน นี่เป็นผลมาจากการเห็นการสร้าง / การตั้งค่า / การกำหนดค่าเป็นความรับผิดชอบของตัวเอง- ไม่ใช่หน้าที่ของรถยนต์ในการสร้างมอเตอร์

ข้อควรพิจารณาด้านประสิทธิภาพ

หรือสิ่งนี้มีผลต่อประสิทธิภาพโดยรวมหรือไม่?

ไม่ แต่หากจะซื่อสัตย์กับคุณอย่างสมบูรณ์

อย่างไรก็ตามแม้ในกรณีนี้มันเป็นเรื่องเล็กน้อยที่คุณไม่จำเป็นต้องดูแล ถ้าในอนาคตคุณต้องเขียนโค้ดสำหรับ Tamagotchi ที่มี CPU 5Mhz และ RAM 2MB เทียบเท่าบางทีคุณอาจต้องใส่ใจกับเรื่องนี้

ในกรณีที่ 99.999% * จะมีประสิทธิภาพที่ดีขึ้นเนื่องจากคุณใช้เวลาในการแก้ไขข้อบกพร่องน้อยลงและมีเวลามากขึ้นในการปรับปรุงอัลกอริทึมที่มีทรัพยากรมาก

* ตัวเลขที่สร้างขึ้นอย่างสมบูรณ์

เพิ่มข้อมูล: "รหัสยาก"

อย่าทำผิดนี่ยังคงเป็น "ฮาร์ดโค้ด" เป็นอย่างมาก - ตัวเลขนั้นเขียนโดยตรงในรหัส การไม่กำหนดรหัสยากจะหมายถึงสิ่งที่ต้องการจัดเก็บค่าเหล่านั้นในไฟล์ข้อความเช่นในรูปแบบ JSON จากนั้นอ่านค่าจากไฟล์นั้น

ในการดำเนินการดังกล่าวคุณต้องเพิ่มรหัสเพื่ออ่านไฟล์จากนั้นแยกวิเคราะห์ JSON หากคุณพิจารณาตัวอย่างอีกครั้ง ในรุ่นที่ไม่ใช่ DI CarหรือMotorตอนนี้ต้องอ่านไฟล์ ไม่เสียงเหมือนว่ามันเข้าท่ามากเกินไป

ในรุ่น DI คุณจะต้องเพิ่มลงในรหัสการตั้งค่าเกม


3
โฆษณาที่กำหนดรหัสยากมีความแตกต่างไม่มากระหว่างรหัสและไฟล์ปรับแต่ง ไฟล์ที่มาพร้อมกับแอปพลิเคชันเป็นแหล่งข้อมูลแม้ว่าคุณจะอ่านแบบไดนามิก การดึงค่าจากรหัสไปยังไฟล์ข้อมูลในรูปแบบ json หรือรูปแบบ 'config' ไม่ได้ช่วยอะไรนอกจากค่าที่ควรจะถูกแทนที่โดยผู้ใช้หรือขึ้นอยู่กับสภาพแวดล้อม
Jan Hudec

3
ฉันทำ Tamagotchi หนึ่งครั้งบน Arduino (16MHz, 2KB)
Jungkook

@JanHudec True จริง ๆ แล้วฉันมีคำอธิบายนานกว่านั้น แต่ตัดสินใจที่จะลบออกเพื่อให้สั้นลงและมุ่งเน้นไปที่ความสัมพันธ์กับ DI มีอีกหลายสิ่งที่ไม่ถูกต้อง 100% โดยรวมแล้วคำตอบนั้นได้รับการปรับให้เหมาะสมที่สุดที่จะผลักดัน "จุด" ของ DI โดยไม่ต้องใช้เวลานานเกินไป หรือพูดให้แตกต่างนี่คือสิ่งที่ฉันอยากได้ยินเมื่อฉันเริ่มต้นด้วย DI
R. Schmitz

17

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

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

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

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

ดังนั้น: มีเหตุผลที่ดีที่คุณจำเป็นต้องใช้Settingประเภท / ค่านี้โดยเฉพาะหรือไม่? มีเหตุผลที่ดีที่รหัสโทรอาจต้องการSettingประเภท / ค่าที่แตกต่างกันหรือไม่? (จำไว้ว่าการทดสอบคือรหัส !)


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

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

7

ตัวอย่างที่คุณให้ไม่ใช่การพึ่งพาการฉีดในความหมายดั้งเดิม การฉีดพึ่งพามักจะหมายถึงการส่งผ่านวัตถุในตัวสร้างหรือโดยใช้ "setter injection" หลังจากที่วัตถุถูกสร้างขึ้นเพื่อตั้งค่าในเขตข้อมูลในวัตถุที่สร้างขึ้นใหม่

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

ตอนนี้ถ้ารหัสเป็นเช่นนี้:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

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

สิ่งนี้มีประโยชน์หากวัตถุการตั้งค่ามีราคาแพงหรือซับซ้อนในการสร้างหรือการตั้งค่าเป็นอินเทอร์เฟซหรือคลาสนามธรรมที่มีการใช้งานที่เป็นรูปธรรมหลายอย่างเพื่อเปลี่ยนพฤติกรรมเวลาทำงาน

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

ดูเหมือนว่าการตั้งค่าสำหรับโปรไฟล์นั้นเฉพาะกับโปรไฟล์นั้น ในกรณีนี้ฉันจะทำอย่างใดอย่างหนึ่งต่อไปนี้:

  1. ยกตัวอย่างวัตถุการตั้งค่าภายในตัวสร้างโปรไฟล์

  2. ผ่านการการตั้งค่าวัตถุในตัวสร้างและคัดลอกผ่านแต่ละเขตข้อมูลที่ใช้กับโปรไฟล์

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


5
The example you give is not dependency injection in the classical sense.- ไม่เป็นไร คน OO มีสิ่งของในสมอง แต่คุณยังคงต้องพึ่งพาบางสิ่ง
Robert Harvey

1
เมื่อคุณพูดถึง“ ในแง่ของความคลาสสิค ” คุณก็คือ @RobertHarvey พูดว่าพูดในแง่ของ OO อย่างหมดจด ในการเขียนโปรแกรมเชิงฟังก์ชันตัวอย่างเช่นการฉีดฟังก์ชันหนึ่งไปยังอีกฟังก์ชันหนึ่ง (ฟังก์ชันลำดับสูงกว่า) คือตัวอย่างคลาสสิกของกระบวนทัศน์การฉีดพึ่งพา
David Arno

3
@ RobertHarvey: ฉันเดาว่าฉันใช้เสรีภาพมากเกินไปกับ "การฉีดพึ่งพา" สถานที่ที่คนส่วนใหญ่มักนึกถึงคำศัพท์และใช้คำนั้นอ้างอิงถึงทุ่งนาบนวัตถุที่ถูก "ฉีด" ในเวลาที่สร้างวัตถุหรือทันทีหลังจากในกรณีของการฉีดเซทเทอร์
Greg Burghardt

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

7

ฉันรู้ว่าฉันมางานปาร์ตี้สายนี้ แต่ฉันรู้สึกว่าประเด็นสำคัญกำลังถูกพลาด

ทำไมฉันต้องทำสิ่งนี้:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

คุณไม่ควร แต่ไม่ใช่เพราะการฉีดพึ่งพาเป็นความคิดที่ไม่ดี เป็นเพราะสิ่งนี้ทำผิด

ให้ดูที่สิ่งนี้โดยใช้รหัส เราจะทำสิ่งนี้:

$profile = new Profile();
$profile->deactivateProfile($setting);

เมื่อเราได้สิ่งเดียวกันนี้:

$setting->isActive = false; // Deactivate profile

แน่นอนว่าดูเหมือนเสียเวลา เมื่อคุณทำเช่นนี้ นี่ไม่ใช่การใช้ Dependency Injection ที่ดีที่สุด มันไม่ใช่การใช้คลาสที่ดีที่สุด

ตอนนี้ถ้าเรามีสิ่งนี้แทน:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

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

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

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

ลองทำสิ่งที่คุณต้องดูแลเมื่อเวลาผ่านไปและดูว่ามันไม่ได้ช่วยอะไร


6

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

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

ทำไมเราทำเช่นนี้? ลองคิดดูสิ คุณเป็นเจ้าของอู่ซ่อมรถที่ต้องการจ้างคนให้เป็นช่างของคุณ คุณจะสอนให้พวกเขาเป็นช่าง (= คุณจะเขียนรหัส)

สิ่งที่จะง่ายขึ้น:

  • สอนช่างให้รู้วิธีแนบสปอยเลอร์กับรถด้วยไขควง
  • สอนให้ช่างสร้างรถสร้างสปอยเลอร์สร้างไขควงแล้วแนบสปอยเลอร์ที่สร้างขึ้นใหม่เข้ากับรถที่สร้างขึ้นใหม่ด้วยไขควงที่สร้างขึ้นใหม่

มีประโยชน์มากมายที่จะไม่มีช่างของคุณสร้างทุกอย่างตั้งแต่เริ่มต้น:

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

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

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

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


ใช่การผกผันของการพึ่งพาต้องใช้ความพยายามเพิ่มขึ้นเล็กน้อยในการเขียนแทนที่จะเป็นการเข้ารหัส hardcoding ใช่มันน่ารำคาญที่ต้องพิมพ์เพิ่มอีก

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

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


สิ่งนี้มีผลต่อประสิทธิภาพโดยรวมหรือไม่

สิ่งนี้จะไม่ส่งผลต่อประสิทธิภาพการทำงานของแอพพลิเคชั่น แต่มันส่งผลกระทบอย่างหนาแน่นเวลาในการพัฒนา (และประสิทธิภาพการทำงาน) ของนักพัฒนา


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

12
ฉันคิดว่าสิ่งทั้งหมดในรถนั้นดูจะสับสนและสับสนเล็กน้อย
Ewan

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

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

2
@gbjbaanb: ไม่มีคำตอบของฉันไม่ได้แม้แต่เจาะลึกจากความแตกต่าง ไม่มีการสืบทอดการใช้อินเตอร์เฟสหรือสิ่งใดที่คล้ายกับการขึ้น / ลงของประเภท คุณกำลังอ่านคำตอบผิดอย่างสมบูรณ์
Flater

0

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

สำหรับการพึ่งพาการฉีดสิ่งนี้สามารถมองเห็นได้ง่ายด้วยการคิดถึงสององค์ประกอบเนื้อหาที่สำคัญที่สุดของการออกแบบ OOP ...

คลัปต่ำ

ข้อต่อในการเขียนโปรแกรมคอมพิวเตอร์ :

ในวิศวกรรมซอฟต์แวร์การมีเพศสัมพันธ์คือระดับของการพึ่งพาซึ่งกันและกันระหว่างโมดูลซอฟต์แวร์ การวัดความเชื่อมโยงรูทีนหรือโมดูลสองอย่างที่เชื่อมโยงกันอย่างใกล้ชิด; ความแข็งแกร่งของความสัมพันธ์ระหว่างโมดูล

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

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

การติดต่อกันสูง

การติดต่อกัน :

ในการเขียนโปรแกรมคอมพิวเตอร์การทำงานร่วมกันหมายถึงระดับที่องค์ประกอบภายในโมดูลอยู่ด้วยกัน

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

ตัวอย่างพื้นฐานสำหรับสิ่งนี้คือรูปแบบ MVC: คุณแยกโมเดลโดเมนออกจากมุมมอง (GUI) และเลเยอร์ควบคุมที่ติดกาวเข้าด้วยกันอย่างชัดเจน

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

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


-1

คุณควรใช้เทคนิคในการแก้ปัญหาที่พวกเขาทำได้ดีเมื่อคุณมีปัญหาเหล่านั้น การผกผันของการพึ่งพาและการฉีดไม่แตกต่างกัน

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

เหตุใดเราต้องการเปลี่ยนแปลงการใช้งานฟังก์ชั่นต่าง ๆ ? มีสองเหตุผลหลัก:

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

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

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

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

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

โปรดทราบว่ามันง่ายที่สุดถ้าคลาสที่ลงทะเบียนสามารถเป็นซิงเกิลไร้สัญชาติได้

คำเตือน

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

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

เราสามารถใช้การผกผันของการพึ่งพาสำหรับบางส่วนของวิธีนี้:

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

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

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

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

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


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

@DavidArno ใช่คุณพูดถูก ฉันได้ปรับคำศัพท์แล้ว
jpmc26

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

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

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