มีการใช้หลักการผกผันการพึ่งพา หลักการผกผันการพึ่งพา สถาปัตยกรรมแบบเลเยอร์ที่มีการผกผันการพึ่งพา


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

ตลอดทั้งบทความ ฉันจะให้คำพูดและตัวอย่างที่จำเป็นทั้งหมดจากแหล่งข้อมูลที่กล่าวมาข้างต้น แต่เพื่อหลีกเลี่ยง "การสปอยล์" และความคิดเห็นของคุณยังคงเป็นกลาง ฉันขอแนะนำให้ใช้เวลา 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 ส่วนผสม ( get;set;) คำอธิบายสตริง ( get;set;) )

โดยเฉพาะคลาสที่แสดงสูตรเค้กวันเกิดมีลักษณะดังนี้:

Class BirthdayCakeRecipe: IRecipe ( public Type PastryType ( get; set; ) ชื่อสตริงสาธารณะ ( get; set;) IList สาธารณะ ส่วนผสม ( get; set; ) สตริงสาธารณะ คำอธิบาย ( get; set; ) สาธารณะ BirthdayCakeReipe() ( ส่วนผสม = รายการใหม่ (); } }

ตอนนี้เรามาดูร้านเบเกอรี่สุดวิเศษของเราที่หัวมุมถนนกันดีกว่า

แน่นอนว่าเราสามารถนำไปใช้กับร้านเบเกอรี่อื่นๆ ได้ ดังนั้นเราจะกำหนดอินเทอร์เฟซพื้นฐานสำหรับร้านเบเกอรี่นั้นด้วย สิ่งที่สำคัญที่สุดสำหรับเบเกอรี่คืออะไร? ความสามารถในการอบผลิตภัณฑ์

อินเทอร์เฟซ IBakery ( IPastry Bake(สูตร IRecipe); )

และนี่คือคลาสที่นำเสนอเบเกอรี่ของเรา:

คลาส NiceBakeryOnTheCornerOFMyStreet ( พจนานุกรม เมนู = พจนานุกรมใหม่ (); โมฆะสาธารณะ AddToMenu(สูตร IRecipe) ( if (!menu.ContainsKey(recipe.Name)) ( menu.Add(recipe.Name, Recipe); ) else ( Console.WriteLine("มีอยู่แล้วในเมนู"); ) ) public IRecipe FindInMenu(ชื่อสตริง) ( if (menu.ContainsKey(name)) ( return menu; ) Console.WriteLine("Sorry...currently we don"t have " + name); return null; ) public IPastry Bake (สูตร IRecipe) ( if (recipe != null) ( IPastry Pastry = Activator.CreateInstance(recipe.PastryType) เป็น IPastry; if (pastry != null) ( Pastry.Name = Recipe.Name; คืน Pastry เป็น IPastry; ) ) กลับเป็นโมฆะ; ))

สิ่งที่เหลืออยู่คือการทดสอบโค้ด:

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(เค้ก); ) )

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

คุณสามารถเพิ่มประเภทของส่วนผสม สูตรสำหรับผลิตภัณฑ์ขนมประเภทต่างๆ ได้อย่างไม่มีที่สิ้นสุด สร้างคลาสอื่นๆ ที่อธิบายร้านเบเกอรี่ ร้านขนม และสถานประกอบการอื่นๆ ที่คล้ายคลึงกัน

ไม่ใช่ผลลัพธ์ที่ไม่ดี และต้องขอบคุณการที่เราพยายามทำให้คลาสต่างๆ มีความเกี่ยวข้องกันน้อยที่สุด

ทีนี้ลองคิดถึงผลที่ตามมาของการละเมิดหลักการผกผันการพึ่งพา:

  1. ความแข็งแกร่ง (คงเป็นเรื่องยากมากที่จะทำการเปลี่ยนแปลงใด ๆ ในระบบ เนื่องจากการเปลี่ยนแปลงแต่ละครั้งส่งผลกระทบต่อส่วนต่าง ๆ มากมาย)
  2. ความเปราะบาง (เมื่อมีการเปลี่ยนแปลงใด ๆ กับส่วนหนึ่งของระบบ ส่วนอื่น ๆ ของระบบจะมีความเสี่ยง และบางครั้งก็ไม่ชัดเจนนักเมื่อมองแวบแรก)
  3. ความไม่สามารถเคลื่อนที่ได้ (คุณสามารถลืมเกี่ยวกับการใช้รหัสซ้ำในระบบอื่นได้เนื่องจากโมดูลเชื่อมต่อกันอย่างแน่นหนา)

ตอนนี้ให้สรุปของคุณเองเกี่ยวกับประโยชน์ของหลักการผกผันการพึ่งพาในโค้ด ฉันคิดว่าคำตอบนั้นชัดเจน

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(); หนังสือพิมพ์();