มีการใช้หลักการผกผันการพึ่งพาอาศัยกันโดยใช้ การผกผันการพึ่งพา
อันที่จริงหลักการทั้งหมด แข็งมีการเชื่อมต่อถึงกันอย่างแน่นหนา และเป้าหมายหลักของพวกเขาคือการช่วยสร้างซอฟต์แวร์คุณภาพสูงที่ปรับขนาดได้ แต่หลักการสุดท้าย แข็งโดดเด่นกว่าพวกเขาจริงๆ อันดับแรก มาดูการกำหนดหลักการนี้กัน ดังนั้น, หลักการผกผันการพึ่งพา (หลักการผกผันการพึ่งพา - DIP): “ขึ้นอยู่กับนามธรรม ไม่มีการพึ่งพาสิ่งใดเป็นพิเศษ. โรเบิร์ต มาร์ติน ผู้เชี่ยวชาญด้านการพัฒนาซอฟต์แวร์ชื่อดัง ยังเน้นย้ำถึงหลักการ จุ่มและนำเสนอง่ายๆ ว่าเป็นผลจากการทำตามหลักการอื่นๆ แข็ง— หลักการเปิด/ปิด และหลักการทดแทน Liskov จำได้ว่าข้อแรกบอกว่าไม่ควรแก้ไขคลาสเพื่อทำการเปลี่ยนแปลงใหม่ และข้อที่สองเกี่ยวข้องกับการสืบทอดและถือว่าการใช้ประเภทพื้นฐานบางประเภทที่ได้รับมาอย่างปลอดภัยโดยไม่ทำลายการทำงานที่ถูกต้องของโปรแกรม เดิมที Robert Martin ได้กำหนดหลักการนี้ไว้ดังนี้:
หนึ่ง). โมดูลระดับบนไม่ควรขึ้นอยู่กับโมดูลระดับล่าง โมดูลทั้งสองระดับต้องขึ้นอยู่กับนามธรรม
2). นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับนามธรรม
นั่นคือจำเป็นต้องพัฒนาคลาสในแง่ของ abstractions ไม่ใช่การใช้งานเฉพาะ และถ้าคุณปฏิบัติตามหลักการ OCPและ LSPนั่นคือสิ่งที่เราจะบรรลุ ดังนั้น ให้กลับไปที่บทเรียนเล็กน้อย ที่นั่นเป็นตัวอย่างเราพิจารณาคลาส กวีซึ่งตอนแรกได้เดินสายไปยังชั้นเรียน กีตาร์, เป็นตัวแทนของเครื่องดนตรีเฉพาะ:
กวีระดับสาธารณะ ( กีตาร์กีตาร์ส่วนตัว กวีสาธารณะ (กีตาร์กีตาร์) ( this.guitar = กีตาร์; ) การเล่นโมฆะสาธารณะ () ( guitar.play (); ) )
กวีชั้นสาธารณะ( กีตาร์กีตาร์ส่วนตัว กวีสาธารณะ (กีตาร์ กีตาร์ ) นี้. กีต้าร์ = กีต้าร์ ; การเล่นเป็นโมฆะสาธารณะ () กีตาร์. เล่น(); |
หากเราต้องการเพิ่มการสนับสนุนเครื่องดนตรีอื่นๆ ในคลาสนี้ เราก็จะต้องแก้ไขคลาสนี้ด้วยวิธีใดวิธีหนึ่ง นี่เป็นการละเมิดหลักการที่ชัดเจน OCP. และคุณอาจสังเกตเห็นแล้วว่าสิ่งเหล่านี้เป็นการฝ่าฝืนหลักการด้วย จุ่มเนื่องจากในกรณีของเราสิ่งที่เป็นนามธรรมของเรานั้นขึ้นอยู่กับรายละเอียด จากมุมมองของการขยายเพิ่มเติมในชั้นเรียนของเรา สิ่งนี้ไม่ดีเลย เพื่อให้ชั้นเรียนของเราเป็นไปตามเงื่อนไขของหลักการ OCPเราได้เพิ่มอินเทอร์เฟซให้กับระบบ อุปกรณ์ซึ่งดำเนินการเรียนเฉพาะที่เป็นตัวแทนของเครื่องดนตรีบางประเภท
ไฟล์ Instrument.java:
อินเทอร์เฟซสาธารณะ ตราสาร (เล่นเป็นโมฆะ (); )
เครื่องมืออินเทอร์เฟซสาธารณะ ( voidplay(); |
ไฟล์ Guitar.java:
class Guitar ใช้เครื่องดนตรี ( @Override public void play() ( System.out.println("Play Guitar!"); ) )
คลาส Guitar ใช้เครื่องมือ ( @แทนที่ การเล่นเป็นโมฆะสาธารณะ () ระบบ. ออก . println("เล่นกีตาร์!"); |
ไฟล์ lute.java:
คลาสสาธารณะ Lute ใช้เครื่องมือ ( @Override public void play() ( System.out.println("Play Lute!"); ) )
คลาสสาธารณะ Lute ใช้เครื่องมือ ( @แทนที่ การเล่นเป็นโมฆะสาธารณะ () ระบบ. ออก . println("เล่นลูท!" ); |
หลังจากนั้นเราก็เปลี่ยนคลาส กวีเพื่อที่ว่าหากจำเป็น เราสามารถแทนที่การนำไปใช้งานด้วยสิ่งที่จำเป็นอย่างแท้จริง สิ่งนี้นำความยืดหยุ่นเพิ่มเติมมาสู่ระบบที่ถูกสร้างขึ้นและลดการทำงานร่วมกัน (การพึ่งพาคลาสที่แข็งแกร่งซึ่งกันและกัน)
คลาสสาธารณะ กวี ( เครื่องดนตรีส่วนตัว กวีสาธารณะ () ( ) การเล่นโมฆะสาธารณะ () ( instrument.play (); ) โมฆะสาธารณะ setInstrument (เครื่องดนตรี) ( this.instrument = เครื่องดนตรี; ) )
กวีชั้นสาธารณะ( เครื่องดนตรีส่วนตัว ; |
2 ตอบกลับ
จุดดี - การผกผันของคำค่อนข้างน่าประหลาดใจ (เนื่องจากหลังจากใช้ DIP โมดูลการพึ่งพาระดับล่างจะไม่ขึ้นอยู่กับโมดูลผู้โทรระดับสูง: ผู้โทรหรือผู้อยู่ในอุปการะตอนนี้เชื่อมต่อกันอย่างหลวม ๆ ผ่านนามธรรมเพิ่มเติม ).
คุณอาจถามว่าทำไมฉันถึงใช้คำว่า "ผกผัน" ตรงไปตรงมา นี่เป็นเพราะวิธีการพัฒนาซอฟต์แวร์แบบเดิมๆ เช่น การวิเคราะห์เชิงโครงสร้างและการออกแบบ มีแนวโน้มที่จะสร้างโครงสร้างซอฟต์แวร์ซึ่งโมดูลระดับสูงต้องอาศัยโมดูลระดับต่ำ และนามธรรมจะขึ้นอยู่กับรายละเอียด อันที่จริง หนึ่งในวัตถุประสงค์ของวิธีการเหล่านี้คือการกำหนดลำดับชั้นของรูทีนย่อยที่อธิบายว่าโมดูลระดับสูงทำการเรียกโมดูลระดับต่ำอย่างไร.... ดังนั้น โครงสร้างการพึ่งพาของโปรแกรมเชิงวัตถุที่ออกแบบมาอย่างดีคือ "กลับด้าน" ในส่วนที่เกี่ยวกับโครงสร้างการพึ่งพา ซึ่งมักจะเป็นผลมาจากวิธีการแบบขั้นตอนดั้งเดิม
จุดหนึ่งที่ควรทราบเมื่ออ่านบทความของลุงบ๊อบบน DIP คือ C++ ไม่มี (และในขณะที่เขียนไม่มี) มีส่วนต่อประสาน ดังนั้นการบรรลุสิ่งที่เป็นนามธรรมใน C ++ มักจะทำได้ผ่านคลาสฐานเสมือนที่เป็นนามธรรม/บริสุทธิ์ ในขณะที่ใน Java หรือ C# สิ่งที่เป็นนามธรรมเพื่อคลายการมีเพศสัมพันธ์มักจะยกเลิกการเชื่อมโยงโดยแยกส่วนต่อประสานออกจากการพึ่งพาและผูกโมดูลระดับสูงกว่ากับอินเทอร์เฟซ
แก้ไขเพียงเพื่อชี้แจง:
"ในบางที่ ฉันยังเห็นว่ามันถูกเรียกว่าการผกผันของการพึ่งพา"
ผกผัน:สลับการจัดการการพึ่งพาจากแอปพลิเคชันไปยังคอนเทนเนอร์ (เช่น Spring)
การฉีดพึ่งพา:
แทนที่จะเขียนรูปแบบโรงงาน ลองฉีดวัตถุลงในคลาสไคลเอ็นต์โดยตรง ให้คลาสไคลเอนต์อ้างถึงอินเทอร์เฟซและเราควรจะสามารถฉีดประเภทที่เป็นรูปธรรมลงในคลาสไคลเอนต์ได้ ด้วยเหตุนี้ คลาสไคลเอ็นต์จึงไม่จำเป็นต้องใช้คีย์เวิร์ดใหม่และแยกจากคลาสที่เป็นรูปธรรมโดยสิ้นเชิง
แล้วการผกผันของการควบคุม (IoC) ล่ะ?
ในการเขียนโปรแกรมแบบดั้งเดิม โฟลว์ของตรรกะทางธุรกิจถูกกำหนดโดยอ็อบเจ็กต์ที่กำหนดแบบสแตติกให้กันและกัน ด้วยการผกผันของการควบคุม โฟลว์จะขึ้นอยู่กับกราฟของออบเจ็กต์ที่สร้างอินสแตนซ์โดยแอสเซมเบลอร์และทำให้เป็นไปได้โดยการโต้ตอบของออบเจ็กต์ที่กำหนดผ่านสิ่งที่เป็นนามธรรม กระบวนการรวมกลุ่มทำได้โดยการฉีดการพึ่งพา แม้ว่าบางคนโต้แย้งว่าการใช้ตัวระบุตำแหน่งบริการยังให้การควบคุมที่กลับกัน
การผกผันของการควบคุมเป็นแนวทางการออกแบบมีจุดประสงค์ดังต่อไปนี้:
- มีการแยกการดำเนินการของงานเฉพาะจากการนำไปใช้งาน
- แต่ละโมดูลสามารถมุ่งเน้นไปที่สิ่งที่มีไว้สำหรับ
- โมดูลไม่ได้ตั้งสมมติฐานใดๆ เกี่ยวกับสิ่งที่ระบบอื่นๆ ทำ แต่อาศัยสัญญาของพวกเขา
- การเปลี่ยนโมดูลไม่มีผลกับโมดูลอื่นๆ
ดูข้อมูลเพิ่มเติม
ปรับปรุงล่าสุด: 03/11/2016
หลักการผกผันการพึ่งพา(หลักการผกผันการพึ่งพา) ใช้เพื่อสร้างเอนทิตีควบคู่กันแบบหลวมๆ ที่ง่ายต่อการทดสอบ แก้ไข และอัปเดต หลักการนี้สามารถกำหนดได้ดังนี้:
โมดูลระดับบนสุดไม่ควรขึ้นอยู่กับโมดูลระดับล่าง ทั้งสองต้องขึ้นอยู่กับนามธรรม
นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับนามธรรม
เพื่อให้เข้าใจหลักการ พิจารณาตัวอย่างต่อไปนี้:
หนังสือคลาส ( ข้อความสตริงสาธารณะ ( รับ; ชุด; ) เครื่องพิมพ์ ConsolePrinter สาธารณะ ( รับ; ชุด; ) โมฆะสาธารณะ พิมพ์() ( Printer.Print(ข้อความ); ) ) คลาส ConsolePrinter ( โมฆะสาธารณะ พิมพ์ (ข้อความสตริง) ( Console.WriteLine (ข้อความ); ) )
คลาส Book ซึ่งแสดงถึงหนังสือ ใช้คลาส ConsolePrinter เพื่อพิมพ์ เมื่อกำหนดเช่นนี้ คลาส Book จะขึ้นอยู่กับคลาส ConsolePrinter นอกจากนี้ เราได้กำหนดไว้อย่างเข้มงวดว่าการพิมพ์หนังสือสามารถทำได้บนคอนโซลโดยใช้คลาส ConsolePrinter เท่านั้น ตัวเลือกอื่นๆ เช่น เอาต์พุตไปยังเครื่องพิมพ์ เอาต์พุตไปยังไฟล์ หรือใช้องค์ประกอบบางอย่างของอินเทอร์เฟซแบบกราฟิก ทั้งหมดนี้ไม่รวมอยู่ในกรณีนี้ สิ่งที่เป็นนามธรรมการพิมพ์หนังสือไม่ได้แยกจากรายละเอียดของคลาส ConsolePrinter ทั้งหมดนี้เป็นการละเมิดหลักการผกผันการพึ่งพา
ตอนนี้ เรามาลองทำให้คลาสของเราสอดคล้องกับหลักการผกผันการพึ่งพาโดยแยก abstractions ออกจากการใช้งานระดับต่ำ:
อินเทอร์เฟซ IPrinter ( void Print(string text); ) class Book ( public string Text ( get; set; ) เครื่องพิมพ์ IPrinter สาธารณะ ( get; set; ) หนังสือสาธารณะ (เครื่องพิมพ์ IPrinter) ( this.Printer = เครื่องพิมพ์; ) public void Print( ) ( Printer.Print(Text); ) ) คลาส ConsolePrinter: IPrinter ( โมฆะสาธารณะ Print(ข้อความสตริง) ( Console.WriteLine("พิมพ์ไปยังคอนโซล"); ) คลาส HtmlPrinter: IPrinter ( โมฆะสาธารณะ พิมพ์ (ข้อความสตริง) ( Console.WriteLine("พิมพ์เป็น html"); ) )
ตอนนี้สิ่งที่เป็นนามธรรมการพิมพ์หนังสือถูกแยกออกจากการใช้งานที่เป็นรูปธรรม ด้วยเหตุนี้ ทั้งคลาส Book และคลาส ConsolePrinter จึงขึ้นอยู่กับสิ่งที่เป็นนามธรรมของ IPrinter นอกจากนี้ ตอนนี้ เรายังสามารถสร้างการใช้งานระดับต่ำเพิ่มเติมของสิ่งที่เป็นนามธรรมของ IPrinter และนำไปใช้แบบไดนามิกในโปรแกรม:
หนังสือหนังสือ = หนังสือใหม่ (ใหม่ ConsolePrinter ()); book.พิมพ์(); book.Printer = HtmlPrinter ใหม่ (); book.พิมพ์();
การผกผันการพึ่งพาเป็นหนึ่งในสำนวนการเขียนโปรแกรมที่สำคัญที่สุด มีคำอธิบายสำนวน (หลักการ) นี้น้อยมากบนอินเทอร์เน็ตภาษารัสเซีย ดังนั้นฉันจึงตัดสินใจพยายามอธิบาย ฉันจะทำตัวอย่างใน Java ในตอนนี้ มันง่ายกว่าสำหรับฉัน แม้ว่าหลักการของการผกผันการพึ่งพาจะใช้ได้กับภาษาการเขียนโปรแกรมใดๆ
คำอธิบายนี้ได้รับการพัฒนาร่วมกับ Vladimir Matveev เพื่อเตรียมชั้นเรียนกับนักเรียน Java
บทความอื่น ๆ จากชุดนี้:
เริ่มต้นด้วยคำจำกัดความของ "การพึ่งพา" การเสพติดคืออะไร? หากโค้ดของคุณใช้คลาสภายในหรือเรียกใช้เมธอดสแตติกของคลาสหรือฟังก์ชันบางอย่างเป็นการภายในหรืออย่างชัดเจน นี่คือการพึ่งพา ให้ฉันอธิบายด้วยตัวอย่าง:
ต่ำกว่าคลาส A ภายในเมธอดที่เรียกว่า someMethod() จะสร้างวัตถุของคลาส B อย่างชัดเจนและเข้าถึงเมธอดของ someMethodOfB()
คลาสสาธารณะ A ( เป็นโมฆะ someMethod() ( B b = new B(); b.someMethodOfB(); ) )
ในทำนองเดียวกัน ตัวอย่างเช่น คลาส B อ้างถึงฟิลด์สแตติกและวิธีการของคลาสระบบอย่างชัดเจน:
คลาสสาธารณะ B ( ถือเป็นโมฆะ someMethodOfB() ( System.out.println("สวัสดีชาวโลก"); ) )
ในทุกกรณีที่คลาสใด ๆ (ประเภท A) สร้างคลาสใด ๆ (ประเภท B) หรือเข้าถึงฟิลด์สแตติกหรือสมาชิกคลาสอย่างชัดเจน สิ่งนี้เรียกว่า ตรงติดยาเสพติด เหล่านั้น. สำคัญ: ถ้าคลาสภายในตัวเองทำงานภายในตัวเองกับคลาสอื่น นี่คือการพึ่งพา หากเขาสร้างคลาสนี้ในตัวเองด้วย ตรงติดยาเสพติด
เกิดอะไรขึ้นกับการพึ่งพาโดยตรง? การพึ่งพาโดยตรงนั้นไม่ดีเพราะคลาสที่สร้างคลาสอื่นภายในตัวเองอย่างอิสระนั้นผูกติดอยู่กับคลาสนี้อย่างแน่นหนา เหล่านั้น. หากมีการเขียนชัดเจนว่า B = new B(); จากนั้นคลาส A จะทำงานกับคลาส B เสมอและไม่มีคลาสอื่น หรือถ้ามันบอกว่า System.out.println("..."); จากนั้นคลาสจะส่งออกไปยัง System.out เสมอและไม่มีที่อื่น
สำหรับชั้นเรียนขนาดเล็ก การพึ่งพาอาศัยกันนั้นไม่น่ากลัว รหัสดังกล่าวอาจใช้งานได้ดี แต่ในบางกรณี เพื่อให้คลาส A ของคุณทำงานได้ในระดับสากลในสภาพแวดล้อมของคลาสที่แตกต่างกัน คลาส A อาจต้องมีการใช้งานคลาสอื่น - การพึ่งพา เหล่านั้น. คุณจะต้องใช้ตัวอย่างเช่นไม่ใช่คลาส B แต่เป็นคลาสอื่นที่มีอินเทอร์เฟซเดียวกันหรือไม่ System.out แต่ตัวอย่างเช่นเอาต์พุตไปยังตัวบันทึก (เช่น log4j)
การพึ่งพาโดยตรงสามารถแสดงแบบกราฟิกได้ดังนี้:
เหล่านั้น. เมื่อคุณสร้างคลาส A ในรหัสของคุณ: A a = new A(); อันที่จริงไม่มีการสร้างคลาส A ขึ้น แต่เป็นลำดับชั้นทั้งหมดของคลาสที่ขึ้นต่อกัน ตัวอย่างที่อยู่ในภาพด้านบน ลำดับชั้นนี้ "เข้มงวด": โดยไม่ต้องเปลี่ยนซอร์สโค้ดของแต่ละคลาส จะไม่สามารถแทนที่คลาสในลำดับชั้นได้ ดังนั้นคลาส A ในการใช้งานดังกล่าวจึงไม่สามารถปรับตัวให้เข้ากับสภาพแวดล้อมที่เปลี่ยนแปลงได้ เป็นไปได้มากว่าจะไม่สามารถใช้มันในรหัสใด ๆ ได้ ยกเว้นรหัสเฉพาะที่คุณเขียน
หากต้องการแยกคลาส A ออกจากการพึ่งพาเฉพาะ ให้ใช้ การฉีดพึ่งพา. การฉีดพึ่งพาคืออะไร? แทนที่จะสร้างคลาสที่ต้องการอย่างชัดเจนในโค้ด การพึ่งพาจะถูกส่งไปยังคลาส A ผ่านตัวสร้าง:
คลาสสาธารณะ A ( ส่วนตัวสุดท้าย B b; สาธารณะ A(B b) ( this.b = b; ) โมฆะสาธารณะ someMethod() ( b.someMethodOfB(); ) )
ที่. คลาส A ตอนนี้ได้รับการพึ่งพาผ่านตัวสร้าง ตอนนี้ ในการสร้างคลาส A คุณต้องสร้างคลาสที่ขึ้นต่อกันก่อน ในกรณีนี้คือ B:
B b = ใหม่ B(); A a = ใหม่ A(b); a.someMethod();
หากทำซ้ำขั้นตอนเดียวกันสำหรับชั้นเรียนทั้งหมดเช่น ส่งผ่านอินสแตนซ์ของคลาส D ไปยังคอนสตรัคเตอร์ของคลาส B ไปยังคอนสตรัคเตอร์ของคลาส D - การพึ่งพา E และ F ฯลฯ จากนั้นคุณจะได้รับโค้ดซึ่งการพึ่งพาทั้งหมดจะถูกสร้างขึ้นในลำดับที่กลับกัน:
G g = ใหม่ G(); H ชั่วโมง = ใหม่ H(); F f = ใหม่(g,h); E e = ใหม่ E(); D d = ใหม่ D(e,f); B b = ใหม่ B(d); A a = ใหม่ A(b); a.someMethod();
กราฟสามารถแสดงได้ดังนี้:
หากคุณเปรียบเทียบภาพ 2 ภาพ - ภาพด้านบนที่มีการขึ้นต่อกันโดยตรงและภาพที่สองที่มีการแทรกการพึ่งพา - คุณจะเห็นว่าทิศทางของลูกศรเปลี่ยนไปเป็นตรงกันข้าม ด้วยเหตุนี้ สำนวนจึงเรียกว่า "การผกผันการพึ่งพา" กล่าวอีกนัยหนึ่งการผกผันการพึ่งพาอยู่ในความจริงที่ว่าคลาสไม่ได้สร้างการพึ่งพาด้วยตัวเอง แต่ได้รับในรูปแบบที่สร้างขึ้นในตัวสร้าง (หรืออย่างอื่น)
เหตุใดการผกผันการพึ่งพาจึงดี ด้วยการผกผันของการพึ่งพา คุณสามารถแทนที่การขึ้นต่อกันทั้งหมดในคลาสโดยไม่ต้องเปลี่ยนโค้ด และนี่หมายความว่าคลาส A ของคุณสามารถกำหนดค่าได้อย่างยืดหยุ่นเพื่อใช้ในโปรแกรมอื่นนอกเหนือจากโปรแกรมที่เขียนในตอนแรก ที่. หลักการผกผันการพึ่งพา (บางครั้งเรียกว่าหลักการของการฉีดขึ้นต่อกัน) เป็นกุญแจสำคัญในการสร้างโค้ดที่ยืดหยุ่น แยกส่วน และนำกลับมาใช้ใหม่ได้
ข้อเสียของการฉีดการพึ่งพานั้นสามารถมองเห็นได้ในแวบแรกเช่นกัน - วัตถุของคลาสที่ออกแบบโดยใช้รูปแบบนี้จะสร้างความยากลำบาก ดังนั้นการพึ่งพาการฉีด (ผกผัน) จึงมักใช้ร่วมกับบางไลบรารีที่ออกแบบมาเพื่ออำนวยความสะดวกในงานนี้ ตัวอย่างเช่น หนึ่งในห้องสมุด Google Guice ซม.