มีการใช้หลักการผกผันการพึ่งพา หลักการผกผันการพึ่งพา สถาปัตยกรรมแบบเลเยอร์ที่มีการผกผันการพึ่งพา
การปฏิเสธความรับผิด: ผู้เขียนบทความนี้ไม่มีเป้าหมายที่จะบ่อนทำลายอำนาจหรือทำให้เพื่อนที่เคารพนับถือเช่น "ลุง" บ็อบมาร์ตินในทางใดทางหนึ่ง มันเกี่ยวกับการคิดอย่างรอบคอบมากขึ้นเกี่ยวกับหลักการของการผกผันการพึ่งพาและการวิเคราะห์ตัวอย่างที่ใช้ในการอธิบาย
ตลอดทั้งบทความ ฉันจะให้คำพูดและตัวอย่างที่จำเป็นทั้งหมดจากแหล่งข้อมูลที่กล่าวมาข้างต้น แต่เพื่อหลีกเลี่ยง "การสปอยล์" และความคิดเห็นของคุณยังคงเป็นกลาง ฉันขอแนะนำให้ใช้เวลา 10-15 นาทีและอ่านคำอธิบายดั้งเดิมของหลักการนี้ในบทความหรือหนังสือ
หลักการของการผกผันการพึ่งพามีลักษณะดังนี้:
A. โมดูลระดับบนสุดไม่ควรขึ้นอยู่กับโมดูลระดับล่าง ทั้งสองจะต้องขึ้นอยู่กับนามธรรม
B. นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
เริ่มจากประเด็นแรกกันก่อน
การแบ่งชั้น
หัวหอมมีชั้น เค้กก็มีชั้น มนุษย์กินคนก็มีชั้น และระบบซอฟต์แวร์ก็มีชั้นเหมือนกัน! – เชร็ค (c)
ระบบที่ซับซ้อนใดๆ ก็ตามจะมีลำดับชั้น: แต่ละเลเยอร์ถูกสร้างขึ้นบนพื้นฐานของเลเยอร์ที่ต่ำกว่าที่ได้รับการพิสูจน์แล้วและทำงานได้ดี วิธีนี้ช่วยให้คุณมุ่งเน้นไปที่ชุดแนวคิดที่จำกัดในแต่ละครั้ง โดยไม่ต้องคำนึงถึงวิธีการนำเลเยอร์ที่ซ่อนอยู่ไปใช้
ด้วยเหตุนี้ เราจึงได้แผนภาพต่อไปนี้:
รูปที่ 1 – รูปแบบการแบ่งชั้นแบบ “ไร้เดียงสา”
จากมุมมองของ Bob Martin แผนการแบ่งระบบออกเป็นชั้นๆ เช่นนี้ ไร้เดียงสา. ข้อเสียของการออกแบบนี้คือ "คุณลักษณะร้ายกาจ: เลเยอร์" นโยบายขึ้นอยู่กับการเปลี่ยนแปลงในทุกชั้นระหว่างทาง คุณประโยชน์. การพึ่งพานี้เป็นสกรรมกริยา.» .
อืม... เป็นคำพูดที่แปลกมาก หากเราพูดถึงแพลตฟอร์ม .NET การขึ้นต่อกันจะเป็นแบบสกรรมกริยาเฉพาะในกรณีที่โมดูลปัจจุบัน "เปิดเผย" โมดูลที่มีระดับต่ำกว่าในอินเทอร์เฟซสาธารณะ กล่าวอีกนัยหนึ่งถ้าเข้า กลไกชั้นมีคลาสสาธารณะที่รับอินสแตนซ์เป็นข้อโต้แย้ง StringUtil(จาก คุณประโยชน์ชั้น) จากนั้นลูกค้าทั้งหมดในระดับนั้น กลไกชั้นติดยาเสพติด คุณประโยชน์ชั้น. มิฉะนั้น จะไม่มีการเคลื่อนผ่านของการเปลี่ยนแปลง: การเปลี่ยนแปลงระดับล่างทั้งหมดจะจำกัดอยู่ที่ระดับปัจจุบันและจะไม่เผยแพร่ข้างต้น.
เพื่อให้เข้าใจแนวคิดของ Bob Martin คุณต้องจำไว้ว่าหลักการของการผกผันการพึ่งพานั้นมีการอธิบายไว้ครั้งแรกในปี 1996 และใช้ภาษา C++ เป็นตัวอย่าง ในบทความต้นฉบับผู้เขียนเองก็เขียนอย่างนั้น ปัญหาการส่งผ่านมีอยู่ในภาษาเท่านั้นโดยไม่มีการแยกส่วนต่อประสานคลาสออกจากการใช้งานอย่างชัดเจน. ในภาษา C++ ปัญหาของการพึ่งพาสกรรมกริยามีความเกี่ยวข้องอย่างแน่นอน: ถ้าเป็นไฟล์ PolicyLayer. ชม.รวมผ่านคำสั่ง "รวม" ชั้นกลไก. ชม.ซึ่งรวมถึง UtilityLayer. ชม.จากนั้นสำหรับการเปลี่ยนแปลงใดๆ ในไฟล์ส่วนหัว UtilityLayer. ชม.(แม้จะอยู่ในส่วน "ปิด" ของคลาสที่ประกาศในไฟล์นี้) เราจะต้องคอมไพล์ใหม่และปรับใช้ไคลเอนต์ทั้งหมดอีกครั้ง อย่างไรก็ตาม ในภาษา C++ ปัญหานี้ได้รับการแก้ไขโดยใช้สำนวน PIml ที่เสนอโดย Herb Sutter และตอนนี้ก็ไม่เกี่ยวข้องกันเช่นกัน
วิธีแก้ไขปัญหานี้จากมุมมองของ Bob Martin คือ:
“เลเยอร์ระดับที่สูงกว่าจะประกาศอินเทอร์เฟซแบบนามธรรมสำหรับบริการที่ต้องการ จากนั้นชั้นล่างจะถูกนำมาใช้เพื่อตอบสนองอินเทอร์เฟซเหล่านี้ คลาสใดก็ตามที่อยู่ที่ระดับบนสุดจะเข้าถึงเลเยอร์ของเลเยอร์ที่อยู่ติดกันผ่านทางอินเทอร์เฟซแบบนามธรรม ดังนั้นชั้นบนจึงไม่ขึ้นอยู่กับชั้นล่าง ในทางตรงกันข้าม ชั้นล่างขึ้นอยู่กับอินเทอร์เฟซบริการแบบนามธรรม ประกาศแล้วในระดับที่สูงขึ้น... ดังนั้น ด้วยการย้อนกลับการพึ่งพา เราจึงสร้างโครงสร้างที่มีความยืดหยุ่น ทนทาน และเคลื่อนที่ได้มากขึ้นในขณะเดียวกัน.»
รูปที่ 2 – เลเยอร์กลับด้าน
ในบางแง่ การแบ่งแยกนี้ก็สมเหตุสมผล ตัวอย่างเช่น เมื่อใช้รูปแบบผู้สังเกตการณ์ วัตถุที่สังเกตได้ (สังเกตได้) จะเป็นตัวกำหนดส่วนต่อประสานสำหรับการโต้ตอบกับโลกภายนอก ดังนั้นจึงไม่มีการเปลี่ยนแปลงภายนอกที่จะส่งผลกระทบต่อมัน
แต่ในทางกลับกัน เมื่อพูดถึงเลเยอร์โดยเฉพาะ ซึ่งโดยปกติจะแสดงเป็นชุดประกอบ (หรือแพ็คเกจในรูปแบบ UML) แนวทางที่เสนอนั้นแทบจะเรียกได้ว่าเป็นไปไม่ได้เลย ตามคำนิยาม คลาสตัวช่วยระดับล่างจะถูกใช้ในโมดูลระดับสูงกว่าที่แตกต่างกันหลายสิบตัว ยูทิลิตี้เลเยอร์จะถูกนำมาใช้ไม่เฉพาะใน ชั้นกลไกแต่ยังอยู่ใน เลเยอร์การเข้าถึงข้อมูล, ชั้นการขนส่ง, เลเยอร์อื่น ๆ. ควรใช้อินเทอร์เฟซที่กำหนดไว้ในโมดูลระดับสูงกว่าทั้งหมดหรือไม่
แน่นอนว่าโซลูชันนี้ไม่เหมาะอย่างยิ่ง โดยเฉพาะอย่างยิ่งเนื่องจากเรากำลังแก้ไขปัญหาที่ไม่มีอยู่บนหลายแพลตฟอร์ม เช่น .NET หรือ Java
แนวคิดเรื่องนามธรรม
คำศัพท์หลายคำฝังแน่นอยู่ในสมองของเราจนเราเลิกสนใจคำศัพท์เหล่านั้น สำหรับโปรแกรมเมอร์ "เชิงวัตถุ" ส่วนใหญ่ หมายความว่าเราหยุดคิดถึงคำศัพท์ที่ใช้มากเกินไป เช่น "นามธรรม" "ความหลากหลาย" "การห่อหุ้ม" ทำไมต้องคิดถึงพวกเขาเพราะทุกอย่างชัดเจนแล้ว? ;)
อย่างไรก็ตาม เพื่อที่จะเข้าใจความหมายของหลักการผกผันการพึ่งพาและส่วนที่สองของคำจำกัดความอย่างถูกต้อง เราจำเป็นต้องกลับไปสู่แนวคิดพื้นฐานข้อใดข้อหนึ่งเหล่านี้ ลองดูคำจำกัดความของคำว่า "นามธรรม" จากหนังสือของ Gradi Bucha:
นามธรรม ระบุลักษณะสำคัญของวัตถุบางอย่างที่แยกความแตกต่างจากวัตถุประเภทอื่นทั้งหมด และด้วยเหตุนี้ จึงกำหนดขอบเขตแนวความคิดของมันอย่างชัดเจนจากมุมมองของผู้สังเกตการณ์
กล่าวอีกนัยหนึ่งสิ่งที่เป็นนามธรรมกำหนดพฤติกรรมที่มองเห็นได้ของวัตถุซึ่งในภาษาโปรแกรมถูกกำหนดโดยอินเทอร์เฟซสาธารณะ (และได้รับการป้องกัน) ของวัตถุ บ่อยครั้งที่เราสร้างโมเดลนามธรรมโดยใช้อินเทอร์เฟซหรือคลาสนามธรรม แม้ว่าจากมุมมองของ OOP จะไม่จำเป็นก็ตาม
กลับไปที่คำจำกัดความ: นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
ตอนนี้เรานึกถึงตัวอย่างอะไรหลังจากที่เราจำได้ว่ามันคืออะไร? สิ่งที่เป็นนามธรรม? เมื่อใดที่นามธรรมเริ่มขึ้นอยู่กับรายละเอียด? ตัวอย่างของการละเมิดหลักการนี้คือคลาสนามธรรม GZipStreamซึ่งใช้เวลา MemoryStreamไม่ใช่คลาสนามธรรม ลำธาร:
คลาสนามธรรม GZipStream ( // นามธรรมของ GZipStream ยอมรับสตรีมที่เป็นรูปธรรมที่ได้รับการป้องกัน GZipStream(MemoryStream memoryStream) () )
อีกตัวอย่างหนึ่งของการละเมิดหลักการนี้คือคลาสพื้นที่เก็บข้อมูลนามธรรมจากชั้นการเข้าถึงข้อมูลที่ได้รับในตัวสร้าง PostgreSqlConnectionหรือสตริงการเชื่อมต่อสำหรับ SQL Server ซึ่งทำให้การใช้งานนามธรรมดังกล่าวเชื่อมโยงกับการใช้งานเฉพาะ แต่นี่คือสิ่งที่ Bob Martin หมายถึงใช่ไหม เมื่อพิจารณาจากตัวอย่างที่ให้ไว้ในบทความหรือในหนังสือ Bob Martin เข้าใจบางสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิงด้วยแนวคิดเรื่อง "นามธรรม"
หลักการจุ่มตามมาร์ติน
เพื่ออธิบายคำจำกัดความของเขา Bob Martin ให้คำอธิบายต่อไปนี้
การตีความหลักการ DIP ที่เรียบง่ายเล็กน้อย แต่ยังคงมีประสิทธิภาพมากนั้นแสดงออกมาตามกฎการศึกษาสำนึกง่ายๆ: “คุณต้องพึ่งพานามธรรม” มันระบุว่าไม่ควรมีการพึ่งพาคลาสเฉพาะ การเชื่อมต่อทั้งหมดในโปรแกรมจะต้องนำไปสู่คลาสนามธรรมหรืออินเทอร์เฟซ
- ไม่ควรมีตัวแปรที่เก็บการอ้างอิงถึงคลาสเฉพาะ
- ไม่ควรมีคลาสที่มาจากคลาสที่เป็นรูปธรรม
- ไม่ควรมีวิธีการที่แทนที่วิธีการที่นำมาใช้ในคลาสพื้นฐานอย่างใดอย่างหนึ่ง
เพื่อแสดงให้เห็นการละเมิดหลักการกรมทรัพย์สินทางปัญญาโดยทั่วไป และประเด็น “ชี้แจง” ประการแรกโดยเฉพาะ จึงยกตัวอย่างต่อไปนี้:
ปุ่มคลาสสาธารณะ ( โคมไฟโคมไฟส่วนตัว; โมฆะสาธารณะ Poll() ( ถ้า (/* เงื่อนไขบางอย่าง */) lamp.TurnOn(); ) )
ทีนี้มาจำอีกครั้งว่ามันคืออะไร สิ่งที่เป็นนามธรรมและตอบคำถามว่า มี “นามธรรม” ที่นี่ซึ่งขึ้นอยู่กับรายละเอียดหรือไม่? ในขณะที่คุณกำลังคิดเกี่ยวกับเรื่องนี้หรือกำลังมองหาย่อหน้าที่มีคำตอบสำหรับคำถามนี้ ฉันต้องการพูดนอกเรื่องเล็กน้อย
โค้ดมีคุณสมบัติที่น่าสนใจประการหนึ่ง ด้วยข้อยกเว้นที่ไม่ค่อยเกิดขึ้น ตัวโค้ดเองก็ไม่สามารถถูกหรือผิดได้ ไม่ว่าจะเป็นจุดบกพร่องหรือคุณสมบัติขึ้นอยู่กับสิ่งที่คาดหวัง แม้ว่าจะไม่มีข้อกำหนดที่เป็นทางการ (ซึ่งเป็นบรรทัดฐาน) โค้ดจะไม่ถูกต้องก็ต่อเมื่อทำสิ่งอื่นนอกเหนือจากที่จำเป็นหรือตั้งใจจะทำ หลักการนี้รองรับการเขียนโปรแกรมตามสัญญา ซึ่งข้อกำหนด (ความตั้งใจ) จะแสดงโดยตรงในโค้ดในรูปแบบของเงื่อนไขเบื้องต้น เงื่อนไขภายหลัง และค่าคงที่
มองไปที่ชั้นเรียน ปุ่มฉันไม่สามารถบอกได้ว่าการออกแบบมีข้อบกพร่องหรือไม่ ฉันสามารถพูดได้อย่างแน่นอนว่าชื่อคลาสไม่ตรงกับการใช้งาน จำเป็นต้องเปลี่ยนชื่อชั้นเรียนเป็น LampButtonหรือลบออกจากชั้นเรียน ปุ่มสนาม โคมไฟ.
Bob Martin ยืนยันว่าการออกแบบนี้มีข้อบกพร่องเพราะ “กลยุทธ์การใช้งานระดับสูงไม่ได้แยกออกจากการใช้งานระดับต่ำ นามธรรมจะไม่แยกออกจากรายละเอียด ในกรณีที่ไม่มีการแยกดังกล่าว กลยุทธ์ระดับบนสุดจะขึ้นอยู่กับโมดูลระดับล่างโดยอัตโนมัติ และนามธรรมจะขึ้นอยู่กับรายละเอียดโดยอัตโนมัติ"
ประการแรก ฉันไม่เห็น "กลยุทธ์ระดับบนสุด" และ "โมดูลระดับล่าง" ในตัวอย่างนี้: จากมุมมองของฉันชั้นเรียน ปุ่มและ โคมไฟอยู่ในระดับนามธรรมเดียวกัน (อย่างน้อยฉันก็ไม่เห็นข้อโต้แย้งใด ๆ ที่ตรงกันข้าม) ความจริงที่ว่าชั้น ปุ่มความสามารถในการควบคุมใครบางคนไม่ได้ทำให้พวกเขามีระดับที่สูงขึ้น ประการที่สอง ไม่มี "สิ่งที่เป็นนามธรรมขึ้นอยู่กับรายละเอียด" ที่นี่ มี "การนำสิ่งที่เป็นนามธรรมไปใช้โดยขึ้นอยู่กับรายละเอียด" ซึ่งไม่เหมือนกันเลย
วิธีแก้ปัญหาของ Martin คือ:
รูปที่ 3 - "การพึ่งพาอาศัยกัน"
วิธีแก้ปัญหานี้ดีกว่าไหม? มาดูกัน…
ข้อได้เปรียบหลักของการกลับค่าการพึ่งพา "ตาม Martin" คือการผกผันความเป็นเจ้าของ ในการออกแบบเดิมเมื่อเปลี่ยนคลาส โคมไฟชั้นเรียนจะต้องเปลี่ยน ปุ่ม. ตอนนี้ชั้น ปุ่ม"เป็นเจ้าของ" อินเทอร์เฟซ ปุ่มเซิร์ฟเวอร์แต่ไม่สามารถเปลี่ยนแปลงได้เนื่องจากการเปลี่ยนแปลงใน “ระดับล่าง” เช่น โคมไฟ. มันตรงกันข้าม: การเปลี่ยนคลาส ปุ่มเซิร์ฟเวอร์เป็นไปได้เฉพาะภายใต้อิทธิพลของการเปลี่ยนแปลงในคลาส Button ซึ่งจะนำไปสู่การเปลี่ยนแปลงในรุ่นต่อ ๆ ไปของคลาส ButonServer!
การกำหนดหลักการผกผันการพึ่งพาประกอบด้วยกฎสองข้อ ซึ่งการปฏิบัติตามนั้นมีผลเชิงบวกอย่างมากต่อโครงสร้างโค้ด:
- โมดูลระดับบนสุดไม่ควรขึ้นอยู่กับโมดูลระดับล่าง ทั้งสองจะต้องขึ้นอยู่กับสิ่งที่เป็นนามธรรม
- นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
ในตอนแรกมันฟังดูไม่น่าดึงดูดนัก และผู้อ่านคงเตรียมพร้อมสำหรับบทความที่น่าเบื่อมากซึ่งมีคำศัพท์มากมาย คำพูดที่ซับซ้อน และตัวอย่าง ซึ่งไม่มีอะไรชัดเจนอยู่แล้ว แต่เปล่าประโยชน์เพราะโดยหลักการแล้วภายใต้หลักการของการผกผันการพึ่งพาทุกอย่างกลับมาอีกครั้งเพื่อการใช้งานที่ถูกต้องและในบริบทของแนวคิดหลัก - การใช้โค้ดซ้ำ
แนวคิดอีกประการหนึ่งที่เกี่ยวข้องในที่นี้คือการเชื่อมโยงประเภทที่อ่อนแอ นั่นคือการลดหรือลดการพึ่งพาซึ่งกันและกัน ซึ่งในความเป็นจริงสามารถทำได้โดยใช้นามธรรมและความหลากหลาย อันที่จริงนี่คือสาระสำคัญของหลักการของการผกผันการพึ่งพา
ตอนนี้เรามาดูตัวอย่างที่จะแสดงให้เห็นอย่างชัดเจนว่าคัปปลิ้งหลวมมีลักษณะอย่างไรในการทำงาน
สมมติว่าเราตัดสินใจสั่งเค้กวันเกิด ด้วยเหตุนี้เราจึงได้ไปที่ร้านเบเกอรี่ที่ยอดเยี่ยมตรงหัวมุมถนน เราพบว่าพวกเขาสามารถอบเค้กให้เราโดยใช้ชื่ออันดังว่า "หรูหรา" ได้หรือไม่ และเมื่อได้รับการตอบรับที่ดี เราก็สั่งไป มันง่ายมาก
ตอนนี้เรามาตัดสินใจว่าจะต้องรวมนามธรรมใดบ้างในโค้ดนี้ ในการดำเนินการนี้ เพียงถามตัวเองสองสามคำถาม:
- ทำไมต้องเค้ก? คุณยังสามารถสั่งพายหรือคัพเค้กได้
- ทำไมต้องเป็นวันเกิดของคุณโดยเฉพาะ? แล้วถ้าเป็นงานแต่งงานหรืองานรับปริญญาล่ะ?
- ทำไมต้อง "หรูหรา" แล้วถ้าฉันชอบ "Anthill" หรือ "Prague" ดีกว่าล่ะ?
- ทำไมร้านเบเกอรี่แห่งนี้ถึงไม่ใช่ร้านขนมอบในใจกลางเมืองหรือที่อื่น?
และแต่ละจุดเหล่านี้ “จะเกิดอะไรขึ้นถ้า” และ “จะเกิดอะไรขึ้นถ้า” เป็นจุดที่จำเป็นต้องมีความสามารถในการขยายโค้ด และด้วยเหตุนี้ การเชื่อมต่อแบบหลวมๆ และคำจำกัดความของประเภทต่างๆ ผ่านทางนามธรรม
ตอนนี้เรามาเริ่มสร้างประเภทกันดีกว่า
เราจะกำหนดอินเทอร์เฟซสำหรับผลงานชิ้นเอกของขนมที่เราสามารถสั่งซื้อได้
อินเทอร์เฟซ IPastry ( ชื่อสตริง ( get; set; ) )
และนี่คือการดำเนินการเฉพาะสำหรับกรณีของเรา - เค้กวันเกิด อย่างที่คุณเห็นตามธรรมเนียมแล้วเค้กวันเกิดเกี่ยวข้องกับเทียน)))
คลาส BirthdayCake: IPastry ( public int NumberOfCandles ( get; set; ) ชื่อสตริงสาธารณะ ( get; set; ) สตริงแทนที่สาธารณะ ToString() ( return String.Format("(0) with (1)candle nices", Name, NumberOfCandles ) ; ) )
ตอนนี้หากเราต้องการเค้กสำหรับงานแต่งงานหรือเพียงสำหรับดื่มชา หรือเราต้องการคัพเค้กหรือพายคัสตาร์ด เราก็มีอินเทอร์เฟซพื้นฐานสำหรับทุกคน
คำถามต่อไปคือผลิตภัณฑ์ขนมมีความแตกต่างกันอย่างไร (ยกเว้นชื่อแน่นอน) พร้อมสูตรแน่นอน!
และแนวคิดของสูตรอาหารประกอบด้วยรายการส่วนผสมและคำอธิบายกระบวนการทำอาหาร ดังนั้น เราจะมีอินเทอร์เฟซแยกต่างหากสำหรับแนวคิดส่วนผสม:
อินเทอร์เฟซ IIngredient ( สตริง IngredientName ( get;set;) ปริมาณสองเท่า ( get; set; ) หน่วยสตริง ( get; set; ) )
ส่วนผสมสำหรับเค้กของเรา ได้แก่ แป้ง เนย น้ำตาล และครีม:
แป้งคลาส: IIngredient ( สตริงสาธารณะ IngredientName ( get; set; ) ปริมาณสองเท่าสาธารณะ ( get; set; ) หน่วยสตริงสาธารณะ ( get; set; ) คุณภาพสตริงสาธารณะ ( get; set; ) ) คลาส Butter: IIngredient ( สตริงสาธารณะ IngredientName ( get; set; ) ปริมาณสองเท่าสาธารณะ ( get; set; ) หน่วยสตริงสาธารณะ ( get; set; ) ) คลาส Sugar: IIngredient ( สตริงสาธารณะ IngredientName ( get; set; ) ปริมาณสองเท่าสาธารณะ ( get; set; ) สตริงสาธารณะ หน่วย ( get; set; ) สตริงสาธารณะ ชนิด ( get; set; ) ) คลาส Creme: IIngredient ( สตริงสาธารณะ IngredientName ( get; set; ) สาธารณะ ปริมาณสองเท่า ( get; set; ) สตริงสาธารณะ หน่วย ( get; set; ) สาธารณะ ไขมันสองเท่า ( get; set; ) )
รายการส่วนผสมอาจแตกต่างกันไปตามสูตรและผลิตภัณฑ์ขนมที่แตกต่างกัน แต่สำหรับสูตรของเรา รายการนี้ก็เพียงพอแล้ว
ตอนนี้ได้เวลาไปยังแนวคิดของสูตรอาหารแล้ว ไม่ว่าเราจะทำอาหารอะไร ไม่ว่าในกรณีใดก็ตาม เราก็รู้ว่ามันเรียกว่าอะไร มันคืออะไร มีส่วนผสมอะไรบ้างในจาน และจะปรุงอย่างไร
อินเทอร์เฟซ IRecipe ( พิมพ์ PastryType ( get; set; ) ชื่อสตริง ( get; set;) IList
โดยเฉพาะคลาสที่แสดงสูตรเค้กวันเกิดมีลักษณะดังนี้:
Class BirthdayCakeRecipe: IRecipe ( public Type PastryType ( get; set; ) ชื่อสตริงสาธารณะ ( get; set;) IList สาธารณะ
ตอนนี้เรามาดูร้านเบเกอรี่สุดวิเศษของเราที่หัวมุมถนนกันดีกว่า
แน่นอนว่าเราสามารถนำไปใช้กับร้านเบเกอรี่อื่นๆ ได้ ดังนั้นเราจะกำหนดอินเทอร์เฟซพื้นฐานสำหรับร้านเบเกอรี่นั้นด้วย สิ่งที่สำคัญที่สุดสำหรับเบเกอรี่คืออะไร? ความสามารถในการอบผลิตภัณฑ์
อินเทอร์เฟซ IBakery ( IPastry Bake(สูตร IRecipe); )
และนี่คือคลาสที่นำเสนอเบเกอรี่ของเรา:
คลาส NiceBakeryOnTheCornerOFMyStreet ( พจนานุกรม
สิ่งที่เหลืออยู่คือการทดสอบโค้ด:
Class Program ( static void Main() ( //สร้าง inctance ของคลาสเบเกอรี่ var Bakery = new NiceBakeryOnTheCornerOFMyStreet(); //เตรียมส่วนผสมสำหรับสูตร varแป้ง = new Flour() ( IngredientName = "Flour", Volume = 1.5 , หน่วย = "kg" ); var Butter = new Butter() ( ชื่อส่วนผสม = "เนย", ปริมาณ = 0.5, หน่วย = "kg" ); var sugar = น้ำตาลใหม่() ( IngredientName = "Sugar", ปริมาณ = 0.7 , Units = "kg" ); var creme = new Creme() ( IngredientName = "Creme", Volume = 1.0, Units = "liters" ); //และนี่คือสูตรของตัวเอง var weddingCakeRecipe = new BirthdayCakeRecipe() ( PastryType = typeof(BirthdayCake), Name = "Birthday Cake Luxury", Description = "คำอธิบายวิธีทำเค้กวันเกิดให้สวยงาม" ); weddingCakeRecipe.Ingredients.Add(แป้ง); weddingCakeRecipe.Ingredients.Add(เนย); weddingCakeRecipe.Ingredients .Add(sugar); weddingCakeRecipe.Ingredients.Add(creme); //เพิ่มสูตรเค้กของเราลงในเมนูเบเกอรี่ Bakery.AddToMenu(weddingCakeRecipe); //ตอนนี้สั่งเลย!! BirthdayCake cake = Bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) เป็น BirthdayCake; //เพิ่มเทียน ;) cake.NumberOfCandles = 10; //และเราก็มาถึงแล้ว !!! Console.WriteLine(เค้ก); ) )
ทีนี้มาดูโค้ดทั้งหมดอีกครั้งและประเมินมัน โค้ดค่อนข้างเรียบง่าย ประเภท บทคัดย่อ ข้อมูล และฟังก์ชันการทำงานได้รับการอธิบายอย่างชัดเจน โค้ดอนุญาตให้ขยายและนำกลับมาใช้ใหม่ได้ แต่ละเซ็กเมนต์สามารถแทนที่ได้อย่างง่ายดายด้วยอีกเซ็กเมนต์ที่ตรงกับประเภทพื้นฐาน และสิ่งนี้จะไม่ทำให้โค้ดที่เหลือเสียหาย
คุณสามารถเพิ่มประเภทของส่วนผสม สูตรสำหรับผลิตภัณฑ์ขนมประเภทต่างๆ ได้อย่างไม่มีที่สิ้นสุด สร้างคลาสอื่นๆ ที่อธิบายร้านเบเกอรี่ ร้านขนม และสถานประกอบการอื่นๆ ที่คล้ายคลึงกัน
ไม่ใช่ผลลัพธ์ที่ไม่ดี และต้องขอบคุณการที่เราพยายามทำให้คลาสต่างๆ มีความเกี่ยวข้องกันน้อยที่สุด
ทีนี้ลองคิดถึงผลที่ตามมาของการละเมิดหลักการผกผันการพึ่งพา:
- ความแข็งแกร่ง (คงเป็นเรื่องยากมากที่จะทำการเปลี่ยนแปลงใด ๆ ในระบบ เนื่องจากการเปลี่ยนแปลงแต่ละครั้งส่งผลกระทบต่อส่วนต่าง ๆ มากมาย)
- ความเปราะบาง (เมื่อมีการเปลี่ยนแปลงใด ๆ กับส่วนหนึ่งของระบบ ส่วนอื่น ๆ ของระบบจะมีความเสี่ยง และบางครั้งก็ไม่ชัดเจนนักเมื่อมองแวบแรก)
- ความไม่สามารถเคลื่อนที่ได้ (คุณสามารถลืมเกี่ยวกับการใช้รหัสซ้ำในระบบอื่นได้เนื่องจากโมดูลเชื่อมต่อกันอย่างแน่นหนา)
ตอนนี้ให้สรุปของคุณเองเกี่ยวกับประโยชน์ของหลักการผกผันการพึ่งพาในโค้ด ฉันคิดว่าคำตอบนั้นชัดเจน
14 คำตอบ
โดยพื้นฐานแล้วมันพูดว่า:
- นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
สำหรับสาเหตุที่สิ่งนี้สำคัญ พูดได้คำเดียวว่า การเปลี่ยนแปลงมีความเสี่ยง และคุณลดความจำเป็นในการเปลี่ยนแปลงในไซต์การโทร ทั้งนี้ขึ้นอยู่กับแนวคิดมากกว่าการนำไปปฏิบัติ
DIP ลดการมีเพศสัมพันธ์ระหว่างส่วนต่างๆ ของโค้ดอย่างมีประสิทธิภาพ แนวคิดก็คือ แม้ว่ามีหลายวิธีในการนำไปใช้ เช่น ตัวบันทึก แต่วิธีที่คุณใช้มันควรจะค่อนข้างคงที่เมื่อเวลาผ่านไป หากคุณสามารถแยกอินเทอร์เฟซที่แสดงถึงแนวคิดของการบันทึกได้ อินเทอร์เฟซนั้นควรจะมีเสถียรภาพมากขึ้นเมื่อเวลาผ่านไปมากกว่าการใช้งาน และไซต์ที่เรียกควรมีความไวต่อการเปลี่ยนแปลงที่คุณอาจทำน้อยกว่ามากโดยการรักษาหรือขยายกลไกการบันทึกนั้น
เนื่องจากการใช้งานเป็นแบบเฉพาะของอินเทอร์เฟซ คุณจึงสามารถเลือกได้ในขณะรันไทม์ว่าการใช้งานใดเหมาะสมที่สุดสำหรับสภาพแวดล้อมเฉพาะของคุณ สิ่งนี้ก็น่าสนใจเช่นกันทั้งนี้ขึ้นอยู่กับกรณี
หนังสือ Agile Software Development, Principles, Patterns and Practices และ Agile Principles, Patterns and Practices ใน C# เป็นแหล่งข้อมูลที่ดีที่สุดสำหรับการทำความเข้าใจเป้าหมายดั้งเดิมและแรงจูงใจเบื้องหลัง Dependency Inversion Principle อย่างถ่องแท้ บทความ "หลักการกลับรายการการพึ่งพา" ก็เป็นแหล่งข้อมูลที่ดีเช่นกัน แต่เนื่องจากเป็นฉบับร่างแบบย่อซึ่งในที่สุดก็จบลงในหนังสือที่กล่าวถึงก่อนหน้านี้ จึงทิ้งการอภิปรายที่สำคัญบางประการเกี่ยวกับแนวคิดของการเป็นเจ้าของแพ็คเกจ และอินเทอร์เฟซที่เป็นกุญแจสำคัญในหลักการนี้แตกต่างจากคำแนะนำทั่วไปสำหรับ "โปรแกรมสำหรับอินเทอร์เฟซ ไม่ใช่การใช้งาน" ที่พบในหนังสือ Design Patterns (Gamma, et al.)
สำหรับการสรุปโดยย่อ หลักการของการผกผันการพึ่งพามีจุดมุ่งหมายหลักคือ เปลี่ยนตามธรรมเนียมแล้วช่องทางการพึ่งพาจากส่วนประกอบ "ระดับสูงกว่า" ไปยังส่วนประกอบ "ระดับล่าง" เพื่อให้ส่วนประกอบ "ระดับล่าง" ขึ้นอยู่กับอินเทอร์เฟซ เป็นของเป็นส่วนประกอบ "ระดับที่สูงกว่า" (หมายเหตุ: องค์ประกอบ "ระดับที่สูงกว่า" ในที่นี้หมายถึงองค์ประกอบที่ต้องการการพึ่งพา/บริการภายนอก และไม่จำเป็นต้องอยู่ในตำแหน่งทางแนวคิดในสถาปัตยกรรมแบบเลเยอร์) อย่างไรก็ตาม ความสัมพันธ์ไม่ได้ ลดลงเท่าที่เธอ กะจากส่วนประกอบที่มีคุณค่าน้อยกว่าในทางทฤษฎีไปจนถึงส่วนประกอบที่มีคุณค่าทางทฤษฎีมากกว่า
สิ่งนี้สามารถทำได้โดยการออกแบบส่วนประกอบที่มีการพึ่งพาภายนอกซึ่งแสดงเป็นอินเทอร์เฟซที่ผู้บริโภคส่วนประกอบจะต้องจัดเตรียมการใช้งาน กล่าวอีกนัยหนึ่ง อินเทอร์เฟซบางตัวจะแสดงสิ่งที่ส่วนประกอบต้องการ ไม่ใช่วิธีที่คุณใช้ส่วนประกอบ (เช่น "INeedSomething" แทนที่จะเป็น "IDoSomething")
สิ่งที่หลักการผกผันการพึ่งพาไม่ได้กล่าวถึงคือแนวทางปฏิบัติง่ายๆ ของการพึ่งพาเชิงนามธรรมโดยใช้อินเทอร์เฟซ (เช่น MyService -> ) แม้ว่าการดำเนินการนี้จะแยกส่วนประกอบออกจากรายละเอียดการใช้งานเฉพาะของการขึ้นต่อกัน แต่จะไม่กลับความสัมพันธ์ระหว่างผู้บริโภคและการขึ้นต่อกัน (เช่น ⇐ Logger.
ความสำคัญของหลักการผกผันการพึ่งพาสามารถสรุปได้เป็นเป้าหมายเดียว - ความสามารถในการนำส่วนประกอบซอฟต์แวร์กลับมาใช้ซ้ำซึ่งต้องอาศัยการพึ่งพาภายนอกสำหรับส่วนหนึ่งของฟังก์ชันการทำงาน (การลงทะเบียน การตรวจสอบ ฯลฯ)
ภายในเป้าหมายทั่วไปของการใช้ซ้ำนี้ เราสามารถแยกแยะการใช้ซ้ำได้ 2 ประเภทย่อย:
การใช้ส่วนประกอบซอฟต์แวร์กับหลายแอปพลิเคชันที่มีการปรับใช้การพึ่งพา (เช่น คุณพัฒนาคอนเทนเนอร์ DI และต้องการจัดเตรียมการบันทึก แต่ไม่ต้องการผูกคอนเทนเนอร์ของคุณกับตัวบันทึกเฉพาะ ดังนั้นทุกคนที่ใช้คอนเทนเนอร์ของคุณจึงต้องใช้การบันทึกด้วย ห้องสมุดที่คุณเลือก)
การใช้ส่วนประกอบซอฟต์แวร์ในบริบทที่เปลี่ยนแปลงไป (เช่น คุณได้พัฒนาส่วนประกอบตรรกะทางธุรกิจที่ยังคงเหมือนเดิมในเวอร์ชันต่างๆ ของแอปพลิเคชัน ซึ่งรายละเอียดการใช้งานมีการพัฒนา)
ในกรณีแรกของการนำส่วนประกอบกลับมาใช้ใหม่ในหลายแอปพลิเคชัน เช่น ด้วยไลบรารีโครงสร้างพื้นฐาน เป้าหมายคือเพื่อให้ผู้บริโภคมีโครงสร้างพื้นฐานพื้นฐานโดยไม่ต้องผูกผู้บริโภคของคุณเข้ากับการขึ้นต่อกันของไลบรารีของคุณเอง เนื่องจากการได้รับการขึ้นต่อกันจากการขึ้นต่อกันดังกล่าวทำให้ผู้บริโภคจำเป็นต้องได้รับ การพึ่งพาเดียวกัน นี่อาจเป็นปัญหาได้เมื่อผู้บริโภคในไลบรารีของคุณตัดสินใจใช้ไลบรารีอื่นสำหรับความต้องการโครงสร้างพื้นฐานเดียวกัน (เช่น NLog และ log4net) หรือหากพวกเขาตัดสินใจที่จะใช้ไลบรารีที่จำเป็นในเวอร์ชันที่ใหม่กว่าซึ่งเข้ากันไม่ได้กับเวอร์ชันที่ต้องการแบบย้อนหลัง โดยห้องสมุดของคุณ
ในกรณีที่สองของการนำส่วนประกอบตรรกะทางธุรกิจกลับมาใช้ใหม่ (เช่น "ส่วนประกอบระดับที่สูงกว่า") เป้าหมายคือการแยกการใช้งานแอปพลิเคชันในขอบเขตหลักจากความต้องการที่เปลี่ยนแปลงของรายละเอียดการใช้งานของคุณ (เช่น การเปลี่ยนแปลง/การอัปเดตไลบรารีถาวร ข้อความการแลกเปลี่ยนไลบรารี) . กลยุทธ์การเข้ารหัส ฯลฯ) ตามหลักการแล้ว การเปลี่ยนแปลงรายละเอียดการใช้งานแอปพลิเคชันไม่ควรทำให้ส่วนประกอบที่ห่อหุ้มตรรกะทางธุรกิจของแอปพลิเคชันเสียหาย
บันทึก. บางคนอาจคัดค้านการอธิบายกรณีที่สองนี้เป็นการนำกลับมาใช้ใหม่จริง โดยเชื่อว่าส่วนประกอบต่างๆ เช่น ส่วนประกอบตรรกะทางธุรกิจที่ใช้ในแอปพลิเคชันที่กำลังพัฒนาตัวเดียวถือเป็นการใช้งานเพียงครั้งเดียว อย่างไรก็ตาม แนวคิดในที่นี้คือการเปลี่ยนแปลงทุกอย่างในรายละเอียดการใช้งานของแอปพลิเคชันแสดงถึงบริบทใหม่และกรณีการใช้งานที่แตกต่างกัน แม้ว่าเป้าหมายสุดท้ายสามารถแยกแยะได้ว่าเป็นการแยกตัวและการพกพาได้
แม้ว่าการปฏิบัติตามหลักการผกผันการพึ่งพาในกรณีที่สองอาจมีประโยชน์บางประการ แต่ก็ควรสังเกตว่าความสำคัญของการนำไปใช้กับภาษาสมัยใหม่ เช่น Java และ C# นั้นลดลงอย่างมาก บางทีถึงจุดที่ไม่เกี่ยวข้อง ตามที่กล่าวไว้ข้างต้น กรมทรัพย์สินทางปัญญาเกี่ยวข้องกับการแยกรายละเอียดการใช้งานออกเป็นแพ็คเกจที่แยกจากกันโดยสิ้นเชิง อย่างไรก็ตาม ในกรณีของแอปพลิเคชันที่กำลังพัฒนา เพียงแค่ใช้อินเทอร์เฟซที่กำหนดไว้ในเงื่อนไขโดเมนธุรกิจจะป้องกันความจำเป็นในการแก้ไขส่วนประกอบระดับสูงกว่า เนื่องจากความต้องการที่เปลี่ยนแปลงของส่วนประกอบรายละเอียดการใช้งาน แม้ว่าท้ายที่สุดแล้วรายละเอียดการใช้งานจะอยู่ในแพ็คเกจเดียวกัน . หลักการส่วนนี้สะท้อนถึงแง่มุมต่างๆ ที่เกี่ยวข้องกับภาษาในขณะที่มีการประมวลผล (เช่น C++) ซึ่งไม่เกี่ยวข้องกับภาษาใหม่ อย่างไรก็ตาม ความสำคัญของหลักการผกผันการพึ่งพานั้นสัมพันธ์กับการพัฒนาส่วนประกอบ/ไลบรารีซอฟต์แวร์ที่นำมาใช้ซ้ำเป็นหลัก
คุณสามารถดูการอภิปรายโดยละเอียดเพิ่มเติมเกี่ยวกับหลักการนี้ซึ่งเกี่ยวข้องกับการใช้อินเทอร์เฟซอย่างง่าย การฉีดการขึ้นต่อกัน และรูปแบบอินเทอร์เฟซแบบแยก
เมื่อเราพัฒนาแอปพลิเคชันซอฟต์แวร์ เราสามารถพิจารณาคลาสระดับต่ำ - คลาสที่ใช้การดำเนินการพื้นฐานและหลัก (การเข้าถึงดิสก์ โปรโตคอลเครือข่าย ฯลฯ) และคลาสระดับสูง - คลาสที่ห่อหุ้มตรรกะที่ซับซ้อน (กระแสธุรกิจ...) .
อย่างหลังต้องอาศัยคลาสระดับต่ำ วิธีธรรมชาติในการนำโครงสร้างดังกล่าวไปใช้คือการเขียนคลาสระดับต่ำและเมื่อใดก็ตามที่เราถูกบังคับให้เขียนคลาสระดับสูงที่ซับซ้อน เนื่องจากคลาสระดับสูงถูกกำหนดจากมุมมองของผู้อื่น นี่ดูเหมือนจะเป็นวิธีที่สมเหตุสมผลในการดำเนินการ แต่นี่ไม่ใช่การออกแบบที่ตอบสนอง จะเกิดอะไรขึ้นถ้าเราจำเป็นต้องเปลี่ยนคลาสระดับต่ำ?
หลักการผกผันการพึ่งพาระบุว่า:
- โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองจะต้องขึ้นอยู่กับนามธรรม
หลักการนี้มีจุดมุ่งหมายเพื่อ "กลับกัน" แนวคิดปกติที่ว่าโมดูลระดับสูงในซอฟต์แวร์จะต้องขึ้นอยู่กับโมดูลระดับล่าง ที่นี่ โมดูลระดับสูงเป็นเจ้าของสิ่งที่เป็นนามธรรม (เช่น การตัดสินใจเลือกวิธีอินเทอร์เฟซ) ที่นำไปใช้โดยโมดูลระดับล่าง ดังนั้นโมดูลระดับล่างจึงขึ้นอยู่กับโมดูลระดับที่สูงกว่า
การใช้การผกผันการพึ่งพาอย่างมีประสิทธิภาพให้ความยืดหยุ่นและความเสถียรตลอดทั้งสถาปัตยกรรมแอปพลิเคชันของคุณ สิ่งนี้จะช่วยให้แอปพลิเคชันของคุณพัฒนาได้อย่างปลอดภัยและเสถียรยิ่งขึ้น
สถาปัตยกรรมแบบเลเยอร์แบบดั้งเดิม
โดยทั่วไปแล้ว ส่วนติดต่อผู้ใช้ของสถาปัตยกรรมแบบเลเยอร์จะขึ้นอยู่กับเลเยอร์ธุรกิจ ซึ่งในทางกลับกันก็ขึ้นอยู่กับเลเยอร์การเข้าถึงข้อมูลด้วย
คุณต้องเข้าใจเลเยอร์ แพ็คเกจ หรือไลบรารี มาดูกันว่าโค้ดเป็นยังไง
เราจะมีไลบรารีหรือแพ็คเกจสำหรับชั้นการเข้าถึงข้อมูล
// DataAccessLayer.dll คลาสสาธารณะ ProductDAO ( )
// BusinessLogicLayer.dll โดยใช้ DataAccessLayer; ProductBO ระดับสาธารณะ ( ProductDAO productDAO ส่วนตัว; )
สถาปัตยกรรมแบบเลเยอร์ที่มีการผกผันการพึ่งพา
การผกผันการพึ่งพาบ่งชี้สิ่งต่อไปนี้:
โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองจะต้องขึ้นอยู่กับนามธรรม
นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
โมดูลระดับสูงและระดับต่ำคืออะไร? เมื่อคิดถึงโมดูลต่างๆ เช่น ไลบรารีหรือแพ็คเกจ โมดูลระดับสูงจะเป็นโมดูลที่มีการพึ่งพาแบบดั้งเดิมและโมดูลระดับต่ำที่ต้องพึ่งพา
กล่าวอีกนัยหนึ่ง ระดับสูงของโมดูลจะเป็นตำแหน่งที่เรียกใช้การดำเนินการ และระดับต่ำที่จะดำเนินการดำเนินการ
หลักการนี้สามารถสรุปได้อย่างสมเหตุสมผล: ไม่ควรมีการพึ่งพาระหว่างปม แต่ควรมีการพึ่งพาสิ่งที่เป็นนามธรรม แต่ตามแนวทางที่เราใช้ เราอาจใช้การพึ่งพาการลงทุนไม่ถูกต้อง แต่นั่นเป็นเพียงนามธรรม
ลองนึกภาพเราปรับโค้ดของเราดังนี้:
เราจะมีไลบรารีหรือแพ็คเกจสำหรับชั้นการเข้าถึงข้อมูลที่กำหนดสิ่งที่เป็นนามธรรม
// DataAccessLayer.dll อินเทอร์เฟซสาธารณะ IProductDAO คลาสสาธารณะ ProductDAO: IProductDAO ()
และตรรกะทางธุรกิจระดับไลบรารีหรือแพ็คเกจอื่นๆ ที่ขึ้นอยู่กับชั้นการเข้าถึงข้อมูล
// BusinessLogicLayer.dll โดยใช้ DataAccessLayer; ProductBO ระดับสาธารณะ (ส่วนตัว IProductDAO productDAO; )
แม้ว่าเราจะพึ่งพานามธรรม แต่ความสัมพันธ์ระหว่างธุรกิจและการเข้าถึงข้อมูลยังคงเหมือนเดิม
เพื่อให้เกิดการผกผันการขึ้นต่อกัน อินเทอร์เฟซการคงอยู่จะต้องถูกกำหนดในโมดูลหรือแพ็คเกจที่มีตรรกะหรือโดเมนระดับสูงอยู่ ไม่ใช่ในโมดูลระดับต่ำ
ขั้นแรกให้กำหนดว่าเลเยอร์โดเมนคืออะไร และลักษณะนามธรรมของการสื่อสารนั้นถูกกำหนดโดยการคงอยู่
// ส่วนต่อประสานสาธารณะ Domain.dll IProductRepository; ใช้ DataAccessLayer; ProductBO คลาสสาธารณะ ( IProductRepository productRepository ส่วนตัว; )
เมื่อระดับการคงอยู่ขึ้นอยู่กับโดเมน ตอนนี้สามารถกลับด้านได้หากมีการกำหนดการขึ้นต่อกัน
// Persistence.dll คลาสสาธารณะ ProductDAO: IProductRepository()
หลักการที่ลึกซึ้งยิ่งขึ้น
สิ่งสำคัญคือต้องเข้าใจแนวคิดนี้ให้ดี ทำให้วัตถุประสงค์และคุณประโยชน์ลึกซึ้งยิ่งขึ้น หากเราอยู่ในกลไกและศึกษาพื้นที่เก็บข้อมูลทั่วไป เราจะไม่สามารถระบุได้ว่าเราจะใช้หลักการพึ่งพาได้ที่ไหน
แต่ทำไมเราถึงกลับการพึ่งพา? เป้าหมายหลักนอกเหนือจากตัวอย่างที่เฉพาะเจาะจงคืออะไร?
โดยปกติจะเป็นเช่นนี้ ช่วยให้สิ่งที่มีเสถียรภาพมากที่สุดซึ่งเป็นอิสระจากสิ่งที่มีเสถียรภาพน้อยกว่าสามารถเปลี่ยนแปลงได้บ่อยขึ้น
การเปลี่ยนประเภทของการคงอยู่หรือฐานข้อมูลหรือเทคโนโลยีในการเข้าถึงฐานข้อมูลเดียวกันนั้นง่ายกว่าตรรกะของโดเมนหรือการดำเนินการที่ออกแบบมาเพื่อสื่อสารด้วยความคงอยู่ ซึ่งทำให้การขึ้นต่อกันที่จะกลับรายการได้เนื่องจากง่ายต่อการเปลี่ยนการคงอยู่ถ้าการเปลี่ยนแปลงนั้นเกิดขึ้น ด้วยวิธีนี้เราจะไม่ต้องเปลี่ยนโดเมน เลเยอร์โดเมนมีความเสถียรที่สุด ดังนั้นจึงไม่ควรขึ้นอยู่กับสิ่งใดเลย
แต่มีมากกว่าตัวอย่างการจัดเก็บข้อมูลนี้ มีหลายสถานการณ์ที่ใช้หลักการนี้ และมีสถาปัตยกรรมที่ใช้หลักการนี้
สถาปัตยกรรม
มีสถาปัตยกรรมที่การผกผันการพึ่งพาเป็นกุญแจสำคัญในคำจำกัดความ ในทุกโดเมน สิ่งนี้สำคัญที่สุด และเป็นนามธรรมที่จะระบุโปรโตคอลการสื่อสารระหว่างโดเมนและส่วนที่เหลือของแพ็คเกจหรือไลบรารี
สถาปัตยกรรมที่สะอาด
สำหรับฉันหลักการของการผกผันการพึ่งพาที่อธิบายไว้ในบทความอย่างเป็นทางการ
ปัญหาใน C ++ คือไฟล์ส่วนหัวมักจะมีการประกาศเขตข้อมูลส่วนตัวและวิธีการ ดังนั้นหากโมดูล C++ ระดับสูงมีไฟล์ส่วนหัวสำหรับโมดูลระดับต่ำ ไฟล์นั้นจะขึ้นอยู่กับไฟล์จริง การดำเนินการรายละเอียดของโมดูลนี้ และเห็นได้ชัดว่านี่ไม่ดีนัก แต่นี่ไม่ใช่ปัญหาในภาษาสมัยใหม่ที่ใช้กันทั่วไปในปัจจุบัน
โมดูลระดับสูงสามารถนำกลับมาใช้ใหม่ได้น้อยกว่าโมดูลระดับต่ำโดยธรรมชาติ เนื่องจากโมดูลแรกมักจะมีความเฉพาะเจาะจงกับแอปพลิเคชัน/บริบทมากกว่าโมดูลหลัง ตัวอย่างเช่น ส่วนประกอบที่ใช้หน้าจอ UI นั้นเป็นระดับสูงสุดและยังมีเฉพาะแอปพลิเคชันมาก (ทั้งหมด?) การพยายามนำส่วนประกอบดังกล่าวกลับมาใช้ใหม่ในแอปพลิเคชันอื่นถือเป็นการต่อต้านและอาจนำไปสู่การพัฒนาที่มากเกินไปเท่านั้น
ดังนั้น การสร้างสิ่งที่เป็นนามธรรมแยกต่างหากในระดับเดียวกันของส่วนประกอบ A ซึ่งขึ้นอยู่กับส่วนประกอบ B (ซึ่งไม่ได้ขึ้นอยู่กับ A) สามารถทำได้ก็ต่อเมื่อส่วนประกอบ A จะมีประโยชน์จริงสำหรับการนำกลับมาใช้ใหม่ในแอปพลิเคชันหรือบริบทที่แตกต่างกัน หากไม่เป็นเช่นนั้น แอปพลิเคชัน DIP จะเป็นการออกแบบที่ไม่ดี
วิธีที่ชัดเจนยิ่งขึ้นในการระบุหลักการผกผันการพึ่งพา:
โมดูลของคุณที่ห่อหุ้มตรรกะทางธุรกิจที่ซับซ้อนไม่ควรขึ้นอยู่กับโมดูลอื่นที่ห่อหุ้มตรรกะทางธุรกิจโดยตรง แต่ควรขึ้นอยู่กับอินเทอร์เฟซกับข้อมูลธรรมดาเท่านั้น
นั่นคือแทนที่จะใช้คลาส Logic ของคุณเหมือนที่ผู้คนทั่วไปทำ:
การพึ่งพาคลาส ( ... ) ลอจิกคลาส ( dep การพึ่งพาส่วนตัว; int doSomething() ( // ตรรกะทางธุรกิจโดยใช้ dep ที่นี่ ) )
คุณควรทำอะไรเช่น:
Class Dependency ( ... ) อินเทอร์เฟซ Data ( ... ) class DataFromDependency ใช้ Data ( private Dependency dep; ... ) class Logic ( int doSomething(Data data) ( // คำนวณบางอย่างด้วย data ) )
Data และ DataFromDependency ควรอยู่ในโมดูลเดียวกันกับ Logic ไม่ใช่กับการพึ่งพา
ทำไมเป็นเช่นนี้?
คำตอบที่ดีและตัวอย่างที่ดีได้ถูกให้ไว้แล้วโดยผู้อื่นที่นี่
จุดของการผกผันการพึ่งพาคือการทำให้ซอฟต์แวร์สามารถนำมาใช้ซ้ำได้
แนวคิดก็คือแทนที่จะต้องใช้โค้ดสองชิ้นที่ต้องพึ่งพาซึ่งกันและกัน แต่กลับพึ่งพาอินเทอร์เฟซแบบนามธรรม จากนั้นคุณจึงนำส่วนหนึ่งส่วนใดกลับมาใช้ซ้ำได้โดยไม่ต้องใช้ส่วนอื่น
โดยปกติจะทำได้โดยการกลับคอนเทนเนอร์ควบคุม (IoC) เช่น Spring ใน Java ในโมเดลนี้ คุณสมบัติของออบเจ็กต์จะถูกตั้งค่าผ่านการกำหนดค่า XML แทนที่จะเป็นออบเจ็กต์ที่ออกไปและค้นหาการขึ้นต่อกัน
ลองจินตนาการถึงรหัสเทียมนี้...
MyClass คลาสสาธารณะ (บริการสาธารณะ myService = ServiceLocator.service; )
MyClass ขึ้นอยู่กับทั้งคลาส Service และคลาส ServiceLocator โดยตรง สิ่งนี้จำเป็นสำหรับทั้งสองอย่างหากคุณต้องการใช้ในแอปพลิเคชันอื่น ทีนี้ลองจินตนาการดูว่า...
คลาสสาธารณะ MyClass ( สาธารณะ IService myService; )
ตอนนี้ MyClass ใช้อินเทอร์เฟซเดียว นั่นคืออินเทอร์เฟซ IService เราจะปล่อยให้คอนเทนเนอร์ IoC ตั้งค่าของตัวแปรนี้จริงๆ
ให้มีโรงแรมแห่งหนึ่งขอวัตถุดิบจากผู้ผลิตอาหาร โรงแรมจะแจ้งชื่อของอาหาร (เช่น ไก่) ให้กับ Food Generator และเครื่องกำเนิดไฟฟ้าจะส่งคืนอาหารที่ร้องขอให้กับโรงแรม แต่โรงแรมไม่สนใจเกี่ยวกับประเภทของอาหารที่ได้รับและเสิร์ฟ ดังนั้นเครื่องกำเนิดไฟฟ้าจึงจัดหาอาหารที่มีป้ายกำกับว่า "อาหาร" ให้กับโรงแรม
การใช้งานนี้ใน JAVA
FactoryClass ด้วยวิธีโรงงาน เครื่องกำเนิดอาหาร
FoodGenerator คลาสสาธารณะ ( Food food; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = new Chicken(); )else food = null; return food; ) )
คำอธิบายประกอบคลาส/อินเทอร์เฟซ
Public abstract class Food ( //ไม่มีคลาสย่อยใดที่จะแทนที่วิธีนี้เพื่อให้มั่นใจในคุณภาพ... public void quality())( String fresh = "This is a fresh " + getName(); String delicious = "This is a delicious " + getName(); System.out.println(fresh); System.out.println(tasty); ) สตริงนามธรรมสาธารณะ getName(); )
ไก่ขายอาหาร(ชั้นคอนกรีต)
Public class Chickenขยายอาหาร ( /*อาหารทุกประเภทจะต้องสดและอร่อย ดังนั้น * พวกเขาจะไม่แทนที่เมธอดระดับซุปเปอร์ "property()"*/ public String getName())( return "Chicken"; ) )
ปลาขายอาหาร (เฉพาะคลาส)
Public class Fishขยายอาหาร ( /*อาหารทุกประเภทจะต้องสดและอร่อย ดังนั้น * อาหารเหล่านั้นจะไม่แทนที่เมธอดระดับซุปเปอร์ "property()"*/ public String getName())( return "Fish"; ) )
ในที่สุด
โรงแรม
โรงแรมระดับสาธารณะ ( public static void main(String args)( //การใช้คลาส Factory.... FoodGenerator foodGenerator = new FoodGenerator(); //วิธีการของโรงงานเพื่อสร้างอินสแตนซ์ของอาหาร... Food food = foodGenerator.getFood( "ไก่"); food.quality(); ) )
อย่างที่เห็น ทางโรงแรมไม่รู้ว่าเป็นไก่หรือปลา เป็นที่ทราบกันเพียงว่านี่คือรายการอาหารเช่น โรงแรมขึ้นอยู่กับระดับอาหาร
คุณอาจสังเกตเห็นว่าคลาสปลาและไก่ใช้คลาสอาหาร และไม่เกี่ยวข้องโดยตรงกับโรงแรม เหล่านั้น. ไก่และปลาก็ขึ้นอยู่กับประเภทอาหารด้วย
ซึ่งหมายความว่าส่วนประกอบระดับสูง (โรงแรม) และส่วนประกอบระดับต่ำ (ปลาและไก่) ขึ้นอยู่กับสิ่งที่เป็นนามธรรม (อาหาร)
สิ่งนี้เรียกว่าการผกผันการพึ่งพา
หลักการผกผันการพึ่งพา (DIP) ระบุว่า
i) โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองจะต้องขึ้นอยู่กับนามธรรม
ii) นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
อินเทอร์เฟซสาธารณะ ICustomer ( string GetCustomerNameById(int id); ) public class ลูกค้า: ICustomer ( //ctor public Customer()() public string GetCustomerNameById(int id) ( return "Dummy Customer Name"; ) ) public class CustomerFactory ( public static ICustomer GetCustomerData() ( ส่งคืนลูกค้าใหม่(); ) ) คลาสสาธารณะ CustomerBLL ( ICustomer _customer; public CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) สตริงสาธารณะ GetCustomerNameById(int id) ( return _customer.GetCustomerNameById(id); ) ) โปรแกรมคลาสสาธารณะ ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )
บันทึก. คลาสควรขึ้นอยู่กับนามธรรมเช่นอินเทอร์เฟซหรือคลาสนามธรรมมากกว่ารายละเอียดที่เป็นรูปธรรม (การใช้งานอินเทอร์เฟซ)
แบ่งปันอัปเดตครั้งล่าสุด: 03/11/2016
หลักการผกผันการพึ่งพาหลักการผกผันการพึ่งพาใช้เพื่อสร้างเอนทิตีคู่ที่หลวมซึ่งง่ายต่อการทดสอบ แก้ไข และอัปเดต หลักการนี้สามารถกำหนดได้ดังนี้:
โมดูลระดับบนสุดไม่ควรขึ้นอยู่กับโมดูลระดับล่าง ทั้งสองจะต้องขึ้นอยู่กับนามธรรม
นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดจะต้องขึ้นอยู่กับนามธรรม
เพื่อให้เข้าใจหลักการนี้ ให้พิจารณาตัวอย่างต่อไปนี้:
Class Book ( ข้อความสตริงสาธารณะ ( get; set; ) public ConsolePrinter Printer ( get; set; ) public void Print() ( Printer.Print(Text); ) ) class ConsolePrinter ( public void Print(string text) ( Console.WriteLine) (ข้อความ); ) )
คลาส Book ซึ่งแสดงถึงหนังสือ ใช้คลาส ConsolePrinter ในการพิมพ์ ด้วยคำจำกัดความนี้ คลาส Book ขึ้นอยู่กับคลาส ConsolePrinter ยิ่งไปกว่านั้น เราได้กำหนดไว้อย่างเคร่งครัดว่าการพิมพ์หนังสือสามารถทำได้บนคอนโซลโดยใช้คลาส ConsolePrinter เท่านั้น ตัวเลือกอื่นๆ เช่น ส่งออกไปยังเครื่องพิมพ์ ส่งออกไปยังไฟล์ หรือใช้องค์ประกอบอินเทอร์เฟซแบบกราฟิกบางอย่าง - ทั้งหมดนี้ไม่รวมอยู่ในกรณีนี้ นามธรรมการพิมพ์หนังสือไม่ได้แยกออกจากรายละเอียดของคลาส ConsolePrinter ทั้งหมดนี้เป็นการละเมิดหลักการผกผันการพึ่งพา
ตอนนี้เรามาลองทำให้คลาสของเราสอดคล้องกับหลักการของการผกผันการพึ่งพา โดยแยกนามธรรมออกจากการใช้งานระดับต่ำ:
อินเทอร์เฟซ IPrinter ( เป็นโมฆะพิมพ์(ข้อความสตริง); ) หนังสือคลาส ( ข้อความสตริงสาธารณะ ( get; set; ) เครื่องพิมพ์ IPrinter สาธารณะ ( get; set; ) หนังสือสาธารณะ(เครื่องพิมพ์ IPrinter) ( this.Printer = เครื่องพิมพ์; ) โมฆะสาธารณะ พิมพ์( ) ( Printer.Print(Text); ) ) class ConsolePrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("Print to console"); ) ) class HtmlPrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("พิมพ์เป็น html"); ) )
ปัจจุบันนามธรรมการพิมพ์หนังสือถูกแยกออกจากการใช้งานที่เป็นรูปธรรม ด้วยเหตุนี้ ทั้งคลาส Book และคลาส ConsolePrinter จึงขึ้นอยู่กับนามธรรมของ IPrinter นอกจากนี้ เรายังสามารถสร้างการใช้งานระดับต่ำเพิ่มเติมของ IPrinter abstraction และนำไปใช้แบบไดนามิกในโปรแกรม:
หนังสือ = หนังสือใหม่ (ConsolePrinter ใหม่ ()); หนังสือพิมพ์(); book.Printer = ใหม่ HtmlPrinter(); หนังสือพิมพ์();