วิธีการประยุกต์ใช้หลักการจำแนกส่วนต่อประสานใน C?


15

ฉันมีโมดูลพูดว่า 'M' ซึ่งมีลูกค้าไม่กี่คนให้พูดว่า 'C1', 'C2', 'C3' ฉันต้องการที่จะแบ่งปัน namespace ของโมดูล M คือการประกาศของ API และข้อมูลที่มันเปิดเผยลงในไฟล์ส่วนหัวในลักษณะที่ -

  1. สำหรับลูกค้าใด ๆ จะเห็นเฉพาะข้อมูลและ API ที่ต้องการเท่านั้น ส่วนที่เหลือของเนมสเปซของโมดูลถูกซ่อนจากไคลเอนต์เช่นเป็นไปตามหลักการแยกส่วน
  2. การประกาศไม่ซ้ำในหลายไฟล์ส่วนหัวเช่นไม่ละเมิด แห้ง
  3. โมดูล M ไม่มีการพึ่งพาลูกค้า
  4. ลูกค้าไม่ได้รับผลกระทบจากการเปลี่ยนแปลงที่เกิดขึ้นในส่วนของโมดูล M ที่ไม่ได้ใช้
  5. ลูกค้าที่มีอยู่ไม่ได้รับผลกระทบจากการเพิ่ม (หรือลบ) ของลูกค้าเพิ่มเติม

ขณะนี้ฉันจัดการเรื่องนี้โดยการแบ่งเนมสเปซของโมดูลขึ้นอยู่กับความต้องการของลูกค้า ตัวอย่างเช่นในภาพด้านล่างส่วนต่างๆของเนมสเปซของโมดูลที่ลูกค้า 3 รายต้องการ ความต้องการของลูกค้ามีการทับซ้อนกัน namespace โมดูลแบ่งออกเป็น 4 ส่วนหัวของไฟล์ที่แยกต่างหาก - 'a', '1', '2' และ '3'

การแบ่งพาร์ติชัน namespace ของโมดูล

อย่างไรก็ตามสิ่งนี้เป็นการละเมิดข้อกำหนดดังกล่าวบางประการเช่น R3 และ R5 ข้อกำหนดที่ 3 ถูกละเมิดเนื่องจากการแบ่งพาร์ติชันนี้ขึ้นอยู่กับลักษณะของลูกค้า นอกจากนี้ยังมีการเพิ่มลูกค้าใหม่การแบ่งพาร์ติชันนี้เปลี่ยนแปลงและละเมิดข้อกำหนด 5 ดังที่เห็นในด้านขวาของภาพด้านบนด้วยการเพิ่มไคลเอนต์ใหม่ตอนนี้ namespace ของโมดูลจะถูกแบ่งออกเป็น 7 ไฟล์ส่วนหัว - 'a ', 'B', 'C', '1', '2 *', '3 *' และ '4' ไฟล์ส่วนหัวมีความหมายสำหรับ 2 ในการเปลี่ยนแปลงไคลเอนต์ที่เก่ากว่าจึงก่อให้เกิดการสร้างใหม่

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

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


1
ทำไมปัญหานี้ถึงเจาะจงสำหรับ C เป็นเพราะ C ไม่มีมรดกหรือไม่
Robert Harvey

การละเมิด ISP ทำให้การออกแบบของคุณดีขึ้นหรือไม่
Robert Harvey

2
C ไม่สนับสนุนแนวคิด OOP อย่างแท้จริง (เช่นอินเทอร์เฟซหรือการสืบทอด) เราทำแฮ็กน้ำมันดิบ (แต่สร้างสรรค์) มองหาการแฮ็คเพื่อจำลองการเชื่อมต่อ โดยทั่วไปแล้วไฟล์ส่วนหัวทั้งหมดเป็นส่วนต่อประสานกับโมดูล
work.bin

1
structคือสิ่งที่คุณใช้ใน C เมื่อคุณต้องการอินเทอร์เฟซ จริงอยู่ที่วิธีการนั้นค่อนข้างยาก คุณอาจพบว่าสิ่งนี้น่าสนใจ: cs.rit.edu/~ats/books/ooc.pdf
Robert Harvey

ฉันไม่สามารถเกิดขึ้นกับเทียบเท่าอินเตอร์เฟซที่ใช้และstruct function pointers
work.bin

คำตอบ:


5

การแยกส่วนอินเทอร์เฟซโดยทั่วไปไม่ควรขึ้นอยู่กับความต้องการของลูกค้า คุณควรเปลี่ยนวิธีการทั้งหมดเพื่อให้บรรลุ ฉันจะพูดว่าปรับอินเทอร์เฟซแบบโมดูลาร์โดยจัดกลุ่มคุณลักษณะต่างๆเป็นกลุ่มที่เชื่อมโยงกัน นั่นคือการจัดกลุ่มจะขึ้นอยู่กับการเชื่อมโยงของคุณสมบัติตัวเองไม่ใช่ความต้องการของลูกค้า ในกรณีนั้นคุณจะมีชุดของอินเทอร์เฟซ I1, I2, ... ฯลฯ ไคลเอ็นต์ C1 อาจใช้ I2 เพียงอย่างเดียว ไคลเอนต์ C2 อาจใช้ I1 และ I5 เป็นต้นโปรดทราบว่าหากลูกค้าใช้มากกว่าหนึ่ง Ii จะไม่เป็นปัญหา หากคุณแยกส่วนต่อประสานออกเป็นโมดูลที่เชื่อมโยงกันนั่นคือหัวใจของเรื่อง

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

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


คุณหมายถึงกลุ่มที่ไม่ปะติดปะต่อหรือไม่ทับซ้อนกันอย่างแน่นอนฉันถือว่า?
Doc Brown เมื่อ

ใช่ปลดและไม่ทับซ้อนกัน
Nazar Merza

3

อินเตอร์เฟสแยกหลักการพูดว่า:

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

มีคำถามที่ยังไม่ได้ตอบสองสามข้อที่นี่ หนึ่งคือ:

เล็กแค่ไหน?

คุณพูด:

ขณะนี้ฉันจัดการเรื่องนี้โดยการแบ่งเนมสเปซของโมดูลขึ้นอยู่กับความต้องการของลูกค้า

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

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

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

นี่คือเหตุผลที่ฉันสนใจเกี่ยวกับหลักการแยกส่วนต่อประสาน มันไม่ใช่สิ่งที่ฉันเชื่อมั่นว่าสำคัญ มันแก้ปัญหาจริง

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

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

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

  2.  

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

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

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

ดังนั้นมันกลับกลายเป็นว่าพวกเขามีขนาดเล็กมากอย่างแน่นอน

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


อีกคำถามที่ไม่ได้รับคำตอบคือ:

ใครเป็นเจ้าของอินเตอร์เฟสเหล่านี้

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

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

นี่คือสองวิธีง่ายๆในการดูการออกแบบอินเตอร์เฟส:

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

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

ดังนั้นใครถูก

พิจารณาปลั๊กอิน:

ป้อนคำอธิบายรูปภาพที่นี่

ใครเป็นเจ้าของอินเตอร์เฟสที่นี่ ลูกค้าหรือไม่ บริการหรือไม่

ปรากฎทั้งคู่

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

ฉันชอบรู้ว่าสิ่งที่ควรรู้เกี่ยวกับอะไรและสิ่งที่ไม่ควรรู้ สำหรับฉัน "รู้อะไรเกี่ยวกับอะไร" เป็นคำถามทางสถาปัตยกรรมที่สำคัญที่สุดคำถามเดียว

มาทำความเข้าใจคำศัพท์กันดีกว่า:

[Client] --> [Interface] <|-- [Service]

----- Flow ----- of ----- control ---->

ลูกค้าคือสิ่งที่ใช้

บริการเป็นสิ่งที่ใช้

Interactor เกิดขึ้นเป็นทั้ง

ISP แจ้งว่าเลิกใช้อินเทอร์เฟซสำหรับลูกค้า ไม่เป็นไรช่วยให้สมัครได้ที่นี่:

  • Presenter(บริการ) ไม่ควรกำหนดให้Output Port <I>ส่วนต่อประสาน อินเทอร์เฟซควรถูก จำกัด ให้แคบลงกับสิ่งที่ต้องการInteractor(ที่นี่ทำหน้าที่เป็นลูกค้า) นั่นหมายถึงอินเทอร์เฟซที่รู้เกี่ยวกับInteractorและการติดตาม ISP จะต้องเปลี่ยนแปลงด้วย และนี่เป็นเรื่องปกติ

  • Interactor(ที่นี่ทำหน้าที่เป็นบริการ) ไม่ควรกำหนดให้Input Port <I>อินเทอร์เฟซ อินเทอร์เฟซควรถูก จำกัด ให้แคบลงตามความต้องการController(ไคลเอนต์) นั่นหมายถึงอินเทอร์เฟซที่รู้เกี่ยวกับControllerและการติดตาม ISP จะต้องเปลี่ยนแปลงด้วย และนี่ก็ไม่เป็นไร

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

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

แต่input port <I>อินเทอร์เฟซไม่สามารถผูกมัดตัวเองกับControllerลูกค้าและมีสิ่งนี้เป็นปลั๊กอินที่แท้จริง นี่คือขอบเขต 'ไลบรารี' ร้านเขียนโปรแกรมที่แตกต่างไปจากเดิมอย่างสิ้นเชิงสามารถเขียนเลเยอร์สีเขียวได้หลายปีหลังจากที่เลเยอร์สีแดงถูกเผยแพร่

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

วิธีหนึ่งในการดึงออกมานั้นคืออะแดปเตอร์ วางไว้ระหว่างไคลเอนต์ที่ชอบControlerและInput Port <I>อินเทอร์เฟซ อะแดปเตอร์ยอมรับInteractorว่าเป็นInput Port <I>และมอบสิทธิ์ให้กับมัน อย่างไรก็ตามมันเปิดเผยเฉพาะสิ่งที่ลูกค้าต้องการControllerความต้องการผ่านอินเตอร์เฟซบทบาทหรืออินเตอร์เฟซที่เป็นเจ้าของโดยเลเยอร์สีเขียว อะแดปเตอร์ไม่ทำตาม ISP มันเอง แต่อนุญาตให้คลาสที่ซับซ้อนกว่าต้องการControllerเพลิดเพลินกับ ISP สิ่งนี้มีประโยชน์หากมีอะแดปเตอร์น้อยกว่าไคลเอนต์เช่นControllerนั้นใช้พวกเขาและเมื่อคุณอยู่ในสถานการณ์ที่ผิดปกติที่คุณข้ามเขตแดนของไลบรารีและแม้ว่าจะมีการเผยแพร่ไลบรารีจะไม่หยุดเปลี่ยน มองไปที่คุณ Firefox ตอนนี้การเปลี่ยนแปลงเหล่านั้นจะทำลายอะแดปเตอร์ของคุณเท่านั้น

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

ฉันรู้ว่าคุณกำลังมองหาหลักการที่เรียบง่าย ISP พยายามเป็นเช่นนั้น แต่มันก็ไม่ได้พูดมาก ฉันเชื่อในมัน ใช่โปรดอย่าบังคับให้ลูกค้าต้องพึ่งพาวิธีการที่พวกเขาไม่ได้ใช้โดยไม่มีเหตุผลที่ดี!

หากคุณมีเหตุผลที่ดีดังกล่าวเป็นสิ่งที่การออกแบบของคุณจะยอมรับปลั๊กอินแล้วจะตระหนักถึงปัญหาที่เกิดขึ้นไม่ได้ติดตามสาเหตุ ISP (มันยากที่จะเปลี่ยนแปลงโดยไม่ทำลายลูกค้า) และวิธีการที่จะลดพวกเขา (เก็บInteractorหรืออย่างน้อยInput Port <I>มุ่งเน้นไปที่หนึ่งที่มีเสถียรภาพ กรณีการใช้งาน)


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

ตอนนี้ฉันกำลังโน้มตัวไปยังบริการที่ให้อินเทอร์เฟซที่สมบูรณ์เช่นเดียวกับในเนมสเปซเต็มรูปแบบและมันก็ขึ้นอยู่กับลูกค้าที่จะเข้าถึงบริการเหล่านี้ผ่านทางอะแดปเตอร์เฉพาะลูกค้า ในแง่ C ซึ่งจะเป็นไฟล์ของฟังก์ชั่น wrappers ที่ลูกค้าเป็นเจ้าของ การเปลี่ยนแปลงในบริการจะบังคับให้คอมไพล์การ์ดใหม่ แต่ไม่จำเป็นต้องเป็นไคลเอ็นต์ .. <
contd

<contd> .. สิ่งนี้จะทำให้เวลาในการสร้างน้อยที่สุดอย่างแน่นอนและทำให้การเชื่อมต่อระหว่างไคลเอนต์และบริการ 'หลวม' ที่ค่าใช้จ่ายของรันไทม์ (เรียกใช้ฟังก์ชั่น wrapper ตัวกลาง) เพิ่มพื้นที่รหัสมันเพิ่มการใช้สแต็ก (โปรแกรมเมอร์) ในการบำรุงรักษาอะแดปเตอร์
work.bin

โซลูชันปัจจุบันของฉันตอบสนองความต้องการของฉันได้แล้วแนวทางใหม่นี้จะใช้ความพยายามมากขึ้นและอาจเป็นการละเมิด YAGNI ฉันจะต้องชั่งน้ำหนักข้อดีข้อเสียของแต่ละวิธีและตัดสินใจว่าจะไปทางไหน
work.bin

1

ดังนั้นจุดนี้:

existent clients are unaffected by the addition (or deletion) of more clients.

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

ที่สอง

 partitioning depends on the nature of clients

ทำไมรหัสของคุณไม่ได้ใช้ DI การผกผันของการพึ่งพาไม่มีอะไรไม่มีอะไรในไลบรารีของคุณควรขึ้นอยู่กับลักษณะลูกค้าของคุณ

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

สำหรับลูกค้าใด ๆ จะเห็นเฉพาะข้อมูลและ API ที่ต้องการเท่านั้น ส่วนที่เหลือของเนมสเปซของโมดูลถูกซ่อนจากไคลเอนต์เช่นเป็นไปตามหลักการแยกส่วนต่อประสาน

ใช่

การประกาศไม่ซ้ำกันในหลายไฟล์ส่วนหัวเช่นไม่ละเมิด DRY โมดูล M ไม่มีการพึ่งพาลูกค้า

ใช่

ลูกค้าไม่ได้รับผลกระทบจากการเปลี่ยนแปลงที่เกิดขึ้นในส่วนของโมดูล M ที่ไม่ได้ใช้

ใช่

ลูกค้าที่มีอยู่ไม่ได้รับผลกระทบจากการเพิ่ม (หรือลบ) ของลูกค้าเพิ่มเติม

ใช่


1

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

การทำซ้ำเอกสารหรือการดำเนินการจะละเมิดแห้ง

ฉันจะไม่รบกวนตัวเองด้วยสิ่งนี้หากฉันไม่ได้เขียนรหัสลูกค้า


0

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

โครงสร้างตัวอย่าง

M.h      // fat header
 - P1    // Partition 1
 - P2    // ... 2
   - P21 // ... 2 section 1
 - P3    // ... 3
C1.c     // Client 1 (Needs to include P1, P3)
C2.c     // ... 2 (Needs to include P2)
C3.c     // ... 3 (Needs to include P1, P21, P3)

Mh

#ifdef P1
#define _PREF_ P1_             // Define Prefix ("PREF") = P1_
 void _PREF_init();            // Some partition specific function
#endif /* P1 */

#ifdef P2
#define _PREF_ P2_
 void _PREF_init();
#endif /* P2 */

#if defined(P21) || defined (P2) // Part 2.1
#define _PREF_ P2_1_
 void _PREF_oddone();
#endif /* P21 */

#ifdef P3
#define _PREF_ P3_
 void _PREF_init();
#endif /* P3 */

Mc

ในไฟล์ Mc คุณจะไม่ต้องใช้ #ifdefs เพราะสิ่งที่คุณใส่ในไฟล์. c จะไม่มีผลกับไฟล์ไคลเอนต์ตราบใดที่ฟังก์ชั่นการใช้ไฟล์ไคลเอนต์ถูกกำหนดไว้

#include "M.h"
#define _PREF_ P1_        
void _PREF_init() { ... };

#define _PREF_ P2_
void _PREF_init() { ... }

#define _PREF_ P2_1_
void _PREF_oddone() { ... }

#define _PREF_ P3_
void _PREF_init() { ... }

C1.c

#define P1     // "invite" P1
#define P3     // "invite" P3
#include "M.h" // Open the door, but only the invited come in.

void main()
{
    P1_init();
    //P2_init();
    //P2_1_oddone();
    P3_init();
}

C2.c

#define P2
#include "M.h

void main()
{
    //P1_init();
    P2_init();
    P2_1_oddone();
    //P3_init();
}

C3.c

#define P1
#define P21
#define P3  
#include "M.h" 

void main()
{
    P1_init();
    //P2_init();
    P2_1_oddone();
    P3_init();
}

อีกครั้งฉันไม่แน่ใจว่านี่คือสิ่งที่คุณถาม ดังนั้นเอาไปด้วยเม็ดเกลือ


Mc มีลักษณะอย่างไร คุณให้คำจำกัดความP1_init() และ P2_init() ?
work.bin

@ work.bin ฉันเข้าใจว่า Mc น่าจะเป็นไฟล์. c แบบง่าย ๆ ยกเว้นการกำหนด namespace ระหว่างฟังก์ชั่น
Sanchke Dellowar

สมมติว่าทั้ง C1 และ C2 มีอยู่ - อะไรP1_init()และP2_init()ลิงก์ไปยังอะไร?
work.bin

ในไฟล์ Mh / Mc ตัวประมวลผลล่วงหน้าจะแทนที่_PREF_ด้วยสิ่งที่กำหนดไว้ล่าสุด ดังนั้น_PREF_init()จะเป็นP1_init()เพราะคำสั่ง #define สุดท้าย แล้วคำสั่งต่อไปกำหนดจะตั้งPREFเท่ากับ P2_ P2_init()จึงสร้าง
Sanchke Dellowar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.