การพึ่งพาการฉีด (DI) ไม่ใช่แนวคิดที่เข้าใจง่าย และการนำไปใช้กับแอปพลิเคชันใหม่หรือที่มีอยู่นั้นยิ่งสร้างความสับสน Jess Smith แสดงให้คุณเห็นถึงวิธีการ dependency insert โดยไม่ต้องใช้คอนเทนเนอร์การฉีดในภาษาการเขียนโปรแกรม C# และ Java ในบทความนี้ ฉันจะแสดงให้คุณเห็นถึงวิธีการใช้ Dependency Injection (DI) ในแอปพลิเคชัน .NET และ Java แนวคิดของการพึ่งพาการฉีดได้รับความสนใจจากนักพัฒนาครั้งแรกในปี 2000 เมื่อ Robert Martin เขียนบทความ "หลักการและรูปแบบการออกแบบ" (ต่อมารู้จักกันในชื่อย่อ
SOLID ) D ใน SOLID หมายถึงการพึ่งพาของการผกผัน (DOI) ซึ่งต่อมากลายเป็นที่รู้จักในชื่อการฉีดการพึ่งพา คำจำกัดความดั้งเดิมและที่พบบ่อยที่สุด: การผกผันการพึ่งพาคือการผกผันของวิธีที่คลาสพื้นฐานจัดการการพึ่งพา บทความต้นฉบับของ Martin ใช้โค้ดต่อไปนี้เพื่อแสดงการพึ่งพาของคลาส
Copy
ในคลาสระดับล่าง
WritePrinter
:
void Copy ( )
{
int c;
while ( ( c = ReadKeyboard ( ) ) != EOF)
WritePrinter ( c) ;
}
ปัญหาแรกที่ชัดเจนคือ หากคุณเปลี่ยนรายการพารามิเตอร์หรือประเภทของเมธอด
WritePrinter
คุณจะต้องใช้การอัปเดตในทุกที่ที่มีการพึ่งพาเมธอดนั้น กระบวนการนี้จะเพิ่มค่าใช้จ่ายในการบำรุงรักษาและอาจเป็นสาเหตุให้เกิดข้อผิดพลาดใหม่ๆ
ปัญหาอีกประการหนึ่ง: คลาส Copy ไม่สามารถนำมาใช้ซ้ำได้อีกต่อไป ตัวอย่างเช่น จะเกิดอะไรขึ้นถ้าคุณต้องการส่งออกอักขระที่ป้อนจากแป้นพิมพ์ไปยังไฟล์แทนที่จะส่งไปยังเครื่องพิมพ์ เมื่อต้องการทำเช่นนี้ คุณสามารถปรับเปลี่ยนคลาสได้
Copy
ดังต่อไปนี้ (ไวยากรณ์ภาษา C++):
void Copy ( outputDevice dev)
{
int c;
while ( ( c = ReadKeyboard ( ) ) != EOF)
if ( dev == printer)
WritePrinter ( c) ;
else
WriteDisk ( c) ;
}
แม้จะมีการแนะนำการพึ่งพาใหม่
WriteDisk
แต่สถานการณ์ก็ไม่ดีขึ้น (แต่ค่อนข้างแย่ลง) เนื่องจากมีการละเมิดหลักการอื่น: “เอนทิตีซอฟต์แวร์นั่นคือคลาสโมดูลฟังก์ชันและอื่น ๆ ควรเปิดเพื่อขยาย แต่ปิดเพื่อ การปรับเปลี่ยน” Martin อธิบายว่าคำสั่งเงื่อนไข if/else ใหม่เหล่านี้ลดความเสถียรและความยืดหยุ่นของโค้ด วิธีแก้ไขคือการกลับการขึ้นต่อกันเพื่อให้วิธีการเขียนและอ่านขึ้นอยู่กับไฟล์
Copy
. แทนที่จะเป็นการขึ้นต่อกันแบบ "แตก" พวกมันจะถูกส่งผ่านตัวสร้าง รหัสที่แก้ไขมีลักษณะดังนี้:
class Reader
{
public :
virtual int Read ( ) = 0 ;
} ;
class Writer
{
public :
virtual void Write ( char ) = 0 ;
} ;
void Copy ( Reader & r, Writer & w)
{
int c;
while ( ( c= r. Read( ) ) != EOF)
w. Write( c) ;
}
ตอนนี้คลาส
Copy
สามารถนำกลับมาใช้ใหม่ได้อย่างง่ายดายด้วยการปรับใช้เมธอดคลาส
Reader
และ
Writer
. คลาส
Copy
ไม่มีข้อมูลใด ๆ เกี่ยวกับโครงสร้างภายในของประเภท
Reader
และ
Writer
ทำให้สามารถนำกลับมาใช้ใหม่ได้ด้วยการใช้งานที่แตกต่างกัน แต่ถ้าทั้งหมดนี้ดูเหมือนเป็น gobbledygook สำหรับคุณ บางทีตัวอย่างต่อไปนี้ใน Java และ C# จะช่วยชี้แจงสถานการณ์ได้
ตัวอย่างใน Java และ C# เพื่อแสดงให้เห็นความง่ายในการฉีดการขึ้นต่อกันโดยไม่ต้องใช้คอนเทนเนอร์การขึ้นต่อกัน เรามาเริ่มด้วยตัวอย่างง่ายๆ ที่สามารถปรับแต่งเพื่อใช้ได้
DI
ในไม่กี่ขั้นตอน สมมติว่าเรามีคลาส
HtmlUserPresentation
ที่เมื่อมีการเรียกใช้เมธอดของคลาสนั้น จะสร้างอินเทอร์เฟซผู้ใช้ HTML นี่เป็นตัวอย่างง่ายๆ:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation ( ) ;
String table = htmlUserPresentation. createTable ( rowTableVals, "Login Error Status" ) ;
โปรเจ็กต์ใดๆ ที่ใช้โค้ดคลาสนี้จะต้องขึ้นอยู่กับคลาส ซึ่ง
HtmlUserPresentation
ส่งผลให้เกิดปัญหาด้านการใช้งานและการบำรุงรักษาตามที่อธิบายไว้ข้างต้น การปรับปรุงจะแนะนำตัวเองทันที: การสร้างอินเทอร์เฟซพร้อมลายเซ็นของวิธีการทั้งหมดที่มีอยู่ในคลาสใน
HtmlUserPresentation
ปัจจุบัน นี่คือตัวอย่างของอินเทอร์เฟซนี้:
public interface IHtmlUserPresentation {
String createTable ( ArrayList rowVals, String caption) ;
String createTableRow ( String tableCol) ;
}
หลังจากสร้างอินเทอร์เฟซแล้ว เราก็แก้ไขคลาส
HtmlUserPresentation
เพื่อใช้งาน กลับไปที่การสร้างอินสแตนซ์ของ type
HtmlUserPresentation
ตอนนี้เราสามารถใช้ประเภทอินเทอร์เฟซแทนประเภทพื้นฐานได้:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation ( ) ;
String table = htmlUserPresentation. createTable ( rowTableVals, "Login Error Status" ) ;
การสร้างอินเทอร์เฟซทำให้เราสามารถใช้การใช้งานอื่นๆ ของ
IHtmlUserPresentation
. ตัวอย่างเช่น หากเราต้องการทดสอบประเภทนี้ เราก็สามารถแทนที่ประเภทพื้นฐาน
HtmlUserPresentation
ด้วยประเภทอื่นที่เรียกว่า ได้
HtmlUserPresentationTest
อย่าง ง่ายดาย การเปลี่ยนแปลงที่ทำจนถึงตอนนี้ทำให้โค้ดง่ายต่อการทดสอบ บำรุงรักษา และปรับขนาด แต่ไม่ทำอะไรเลยเพื่อนำมาใช้ซ้ำ เนื่องจาก
HtmlUserPresentation
คลาสทั้งหมดที่ใช้ประเภทนี้ยังคงตระหนักถึงการมีอยู่ของมัน หากต้องการลบการพึ่งพาโดยตรงนี้ คุณสามารถส่งประเภทอินเทอร์เฟซ
IHtmlUserPresentation
ไปยังตัวสร้าง (หรือรายการพารามิเตอร์ของวิธีการ) ของคลาสหรือวิธีการที่จะใช้:
public UploadFile ( IHtmlUserPresentation htmlUserPresentation)
ขณะนี้ Constructor
UploadFile
สามารถเข้าถึงฟังก์ชันการทำงานทั้งหมดของ type
IHtmlUserPresentation
แต่ไม่รู้อะไรเลยเกี่ยวกับโครงสร้างภายในของคลาสที่ใช้อินเทอร์เฟซนี้
UploadFile
ในบริบทนี้ การแทรกประเภทเกิดขึ้นเมื่ออินสแตน ซ์ของคลาสถูกสร้างขึ้น ประเภทอินเทอร์เฟ
IHtmlUserPresentation
ซจะสามารถนำมาใช้ซ้ำได้โดยการส่งต่อการใช้งานที่แตกต่างกันไปยังคลาสหรือวิธีการที่แตกต่างกันซึ่งต้องการฟังก์ชันการทำงานที่แตกต่างกัน
บทสรุปและคำแนะนำในการรวมวัสดุ คุณได้เรียนรู้เกี่ยวกับการพึ่งพาการฉีดและคลาสนั้นขึ้นอยู่กับแต่ละคลาสโดยตรงเมื่อคลาสใดคลาสหนึ่งสร้างอินสแตนซ์อีกคลาสเพื่อเข้าถึงฟังก์ชันการทำงานของประเภทเป้าหมาย หากต้องการแยกการพึ่งพาโดยตรงระหว่างทั้งสองประเภท คุณควรสร้างอินเทอร์เฟซ อินเทอร์เฟซช่วยให้ประเภทสามารถรวมการใช้งานที่แตกต่างกัน ขึ้นอยู่กับบริบทของฟังก์ชันการทำงานที่ต้องการ โดยการส่งประเภทอินเทอร์เฟซไปยังตัวสร้างคลาสหรือเมธอด คลาส/เมธอดที่ต้องการฟังก์ชันการทำงานจะไม่ทราบรายละเอียดใดๆ เกี่ยวกับประเภทการนำอินเทอร์เฟซไปใช้ ด้วยเหตุนี้ ประเภทอินเทอร์เฟซจึงสามารถนำมาใช้ซ้ำในคลาสต่างๆ ที่ต้องการลักษณะการทำงานที่คล้ายกันแต่ไม่เหมือนกัน
หากต้องการทดลองใช้การขึ้นต่อกันของโค้ด ให้ดูที่โค้ดของคุณจากแอปพลิเคชันหนึ่งรายการขึ้นไป และลองแปลงประเภทพื้นฐานที่มีการใช้งานหนักเป็นอินเทอร์เฟซ
เปลี่ยนคลาสที่สร้างอินสแตนซ์ประเภทฐานนี้โดยตรงเพื่อใช้ประเภทอินเทอร์เฟซใหม่นี้และส่งผ่านรายการตัวสร้างหรือพารามิเตอร์ของวิธีการคลาสที่จะใช้
สร้างการใช้งานทดสอบเพื่อทดสอบอินเทอร์เฟซประเภทนี้ เมื่อโค้ดของคุณได้รับการปรับโครงสร้างใหม่แล้วDI
คุณจะนำไปใช้ได้ง่ายขึ้น และคุณจะสังเกตได้ว่าแอปพลิเคชันของคุณมีความยืดหยุ่นมากขึ้นเพียงใดในแง่ของการใช้ซ้ำและการบำรุงรักษา
GO TO FULL VERSION