ข้อต่อหลวมในการออกแบบเชิงวัตถุ


16

ฉันพยายามเรียนรู้ GRASP และฉันพบสิ่งนี้อธิบาย ( ที่หน้า 3 ) เกี่ยวกับการมีเพศสัมพันธ์ต่ำและฉันรู้สึกประหลาดใจมากเมื่อฉันพบสิ่งนี้:

พิจารณาวิธีการaddTrackสำหรับAlbumชั้นเรียนสองวิธีที่เป็นไปได้คือ:

addTrack( Track t )

และ

addTrack( int no, String title, double duration )

วิธีใดลดข้อต่อ อย่างที่สองทำเนื่องจากคลาสที่ใช้คลาส Album ไม่จำเป็นต้องทราบคลาส Track โดยทั่วไปพารามิเตอร์สำหรับเมธอดควรใช้ประเภทฐาน (int, char ... ) และคลาสจากแพ็กเกจ java. *

ฉันมักจะพูดพล่ามกับเรื่องนี้; ฉันเชื่อว่าaddTrack(Track t)ดีกว่าaddTrack(int no, String title, double duration)เนื่องจากเหตุผลหลายประการ:

  1. มันจะดีกว่าเสมอสำหรับวิธีการที่จะทำให้พารามิเตอร์น้อยลงที่สุดเท่าที่จะเป็นไปได้ (ตาม Clean Code ของลุงบ็อบไม่มีหรืออย่างใดอย่างหนึ่งที่ดีกว่า, 2 ในบางกรณีและ 3 ในกรณีพิเศษมากกว่า 3 ต้องการ refactoring .

  2. หากaddTrackเป็นวิธีการของอินเตอร์เฟซและข้อกำหนดที่ต้องการให้ a Trackควรมีข้อมูลเพิ่มเติม (เช่นปีหรือประเภท) ดังนั้นอินเทอร์เฟซที่จะต้องมีการเปลี่ยนแปลงและเพื่อให้วิธีการที่ควรสนับสนุนพารามิเตอร์อื่น

  3. การห่อหุ้มถูกทำลาย ถ้าaddTrackอยู่ในอินเตอร์เฟซแล้วมันไม่ควรจะรู้ internals Trackของ

  4. เป็นจริงมากขึ้นควบคู่ไปกับวิธีที่สองด้วยพารามิเตอร์จำนวนมาก สมมติว่าnoพารามิเตอร์ต้องเปลี่ยนจากintเป็นlongเพราะมีมากกว่าMAX_INTแทร็ก (หรือด้วยเหตุผลใดก็ตาม); ดังนั้นทั้งสองTrackและวิธีการจะต้องมีการเปลี่ยนแปลงในขณะที่ถ้าวิธีการจะมีaddTrack(Track track)เพียงTrackจะมีการเปลี่ยนแปลง

อาร์กิวเมนต์ 4 ข้อทั้งหมดเชื่อมโยงกันจริง ๆ และบางข้อขัดแย้งกัน

วิธีไหนดีกว่ากัน


2
นี่เป็นเอกสารที่อาจารย์หรือผู้ฝึกสอนรวมตัวกันหรือเปล่า? จาก URL ของลิงก์ที่คุณให้ดูเหมือนว่าเป็นคลาส แต่ฉันไม่เห็นเครดิตใด ๆ ในเอกสารว่าใครเป็นผู้สร้าง หากนี่เป็นส่วนหนึ่งของชั้นเรียนฉันขอแนะนำให้คุณถามคำถามเหล่านี้กับบุคคลที่ให้เอกสาร ฉันเห็นด้วยกับเหตุผลของคุณโดยวิธี - มันดูเหมือนจะชัดเจนสำหรับฉันว่าคลาสอัลบัมจะต้องการทราบเกี่ยวกับคลาสของการติดตาม
ดีเร็ก

สุจริตทุกครั้งที่ฉันอ่านเกี่ยวกับ "วิธีปฏิบัติที่ดีที่สุด" ฉันจะเอาเกลือไปด้วย!
AraK

@Derek ฉันพบเอกสารโดยค้นหา Google สำหรับ "ตัวอย่างรูปแบบการจับ" ฉันไม่ได้เป็นคนเขียน แต่เนื่องจากมาจากมหาวิทยาลัยฉันเชื่อว่าเชื่อถือได้ ฉันกำลังมองหาตัวอย่างตามข้อมูลที่ให้และไม่สนใจแหล่งที่มา
m3th0dman

4
@ m3th0dman "แต่เนื่องจากมาจากมหาวิทยาลัยฉันเชื่อว่าเชื่อถือได้" สำหรับฉันเพราะมันมาจากมหาวิทยาลัยฉันคิดว่ามันไม่น่าเชื่อถือ ฉันไม่เชื่อใจคนที่ไม่ได้ทำงานในโครงการหลายปีที่พูดถึงแนวปฏิบัติที่ดีที่สุดในการพัฒนาซอฟต์แวร์
AraK

1
@AraK ความน่าเชื่อถือไม่ได้หมายความว่าไม่ต้องสงสัยเลย และนั่นคือสิ่งที่ฉันทำที่นี่ถามมัน
m3th0dman

คำตอบ:


15

จุดสามจุดแรกของคุณนั้นเกี่ยวกับหลักการอื่นที่ไม่ใช่การแต่งงานกัน คุณต้องสร้างสมดุลระหว่างหลักการออกแบบที่ขัดแย้งกันอยู่เสมอ

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

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

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

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

addNetworkTrack(int no, string title, double duration, URL location)

การมีเพศสัมพันธ์ของคุณเป็นสองเท่าอย่างมีประสิทธิภาพต้องการโมดูลแม้แต่ที่ไม่สนใจสิ่งที่เฉพาะเครือข่ายที่จะยังคงติดตามมันเพื่อที่จะผ่านมันไปได้

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


1
+ การมีเพศสัมพันธ์กับดึกดำบรรพ์ยังคงมีเพศสัมพันธ์ไม่ว่ามันจะหั่นเป็นอย่างไร
JustinC

+1 สำหรับการกล่าวถึงการเพิ่มตัวเลือก URL / เอฟเฟกต์คลื่น
user949300

4
+1 อ่านที่น่าสนใจเกี่ยวกับเรื่องนี้ก็คือการอภิปรายของหลักการพึ่งพาการผกผันในกรมทรัพย์สินทางปัญญาในป่าซึ่งการใช้งานของชนิดดั้งเดิมจะเห็นว่าเป็น"กลิ่น" ดั้งเดิมหลงใหลกับวัตถุมูลค่าเป็นเครื่องมือแก้ไข สำหรับฉันแล้วดูเหมือนว่าจะเป็นการดีกว่าที่จะส่งผ่านแทร็กออบเจ็กต์ที่เป็นกลุ่มดั้งเดิมทั้งหมด ... และถ้าคุณต้องการหลีกเลี่ยงการพึ่งพา / การมีเพศสัมพันธ์กับคลาสที่เฉพาะเจาะจงให้ใช้อินเทอร์เฟซ
Marjan Venema

คำตอบที่ได้รับการยอมรับเพราะอธิบายเกี่ยวกับความแตกต่างระหว่างข้อต่อระบบทั้งหมดและข้อต่อโมดูล
m3th0dman

10

คำแนะนำของฉันคือ:

ใช้

addTrack( ITrack t )

แต่ต้องแน่ใจว่าITrackเป็นส่วนต่อประสานและไม่ใช่คลาสที่เป็นรูปธรรม

อัลบั้มไม่รู้จัก internals ของผู้ITrackดำเนินการ ITrackมันเป็นคู่เดียวที่จะทำสัญญาที่กำหนดโดย

ฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่สร้างการมีเพศสัมพันธ์น้อยที่สุด


1
ฉันเชื่อว่า Track เป็นเพียงการโอนย้ายข้อมูลถั่ว / วัตถุที่มีเพียงฟิลด์และ getters / setters เหนือพวกเขา; จำเป็นต้องมีอินเตอร์เฟสในกรณีนี้หรือไม่?
m3th0dman

6
จำเป็น? อาจจะไม่. ชี้นำใช่ ความหมายที่เป็นรูปธรรมของแทร็กสามารถและจะพัฒนาขึ้น แต่สิ่งที่คลาสการบริโภคต้องการจากมันอาจจะไม่
JustinC

2
@ m3th0dman ขึ้นอยู่กับ abstractions เสมอไม่ใช่ concretions สิ่งนั้นใช้ได้โดยไม่คำนึงว่าTrackเป็นคนโง่หรือฉลาด Trackคือการสรุป ITrackอินเตอร์เฟสเป็นนามธรรม วิธีการที่คุณจะได้รับการ abled ITrackจะมีแตกต่างกันของเพลงในอนาคตตราบเท่าที่พวกเขาปฏิบัติตาม
Tulains Córdova

4
ฉันเห็นด้วยกับความคิด แต่เสียคำนำหน้า 'ฉัน' จาก Clean Code โดย Robert Martin หน้า 24: "ฉันคนก่อนหน้านี้ที่พบเห็นได้ทั่วไปในยุคปัจจุบันเป็นสิ่งที่ทำให้ไขว้เขวที่ข้อมูลที่ดีที่สุดและมากที่สุดที่แย่ที่สุดฉันไม่ต้องการให้ผู้ใช้ของฉันรู้ว่า อินเตอร์เฟซ."
Benjamin Brumfield

1
@BenjaminBrumfield คุณพูดถูก ฉันไม่ชอบคำนำหน้าเช่นกันแม้ว่าฉันจะออกคำตอบเพื่อความชัดเจน
Tulains Córdova

4

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

วิธีตัวอย่างแรกถือว่าสมมติว่าแทร็กกำลังเริ่มอินสแตนซ์นอกคลาสอัลบั้มดังนั้นอย่างน้อยที่สุดเราสามารถสรุปได้ว่าอินสแตนซ์ของคลาสแทร็กนั้นไม่ได้เชื่อมโยงกับคลาสอัลบั้ม

หากแนวปฏิบัติที่ดีที่สุดชี้ให้เห็นว่าเราไม่เคยมีชั้นอ้างอิงหนึ่งชั้นที่สองการเขียนโปรแกรมเชิงวัตถุทั้งหมดจะถูกโยนออกไปนอกหน้าต่าง


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

1
@TMN การมีเพศสัมพันธ์เป็นพิเศษคือวิธีที่ฉันบอกเป็นนัยว่าตัวอย่างที่สองน่าจะจบลงด้วยการสร้างวัตถุติดตามใหม่ การสร้างอินสแตนซ์ของวัตถุนั้นถูกเชื่อมโยงกับวิธีที่ควรเพิ่มการติดตามวัตถุลงในรายการเรียงลำดับบางอย่างในวัตถุอัลบั้ม (การทำลายหลักการความรับผิดชอบเดี่ยว) หากวิธีการสร้างแทร็คที่เคยมีการเปลี่ยนแปลงวิธีการ addTrack () ก็จะต้องมีการเปลี่ยนแปลง นี่ไม่ใช่ในกรณีของตัวอย่างแรก
Derek

3

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

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

คุณพูดถึง encapsulation นั้นยากจน แต่มันไม่ใช่ Albumจะต้องรู้ถึง internals ของTrackและถ้าคุณไม่ได้ใช้วัตถุAlbumจะต้องรู้ว่าทุกชิ้นส่วนของข้อมูลที่ส่งผ่านไปก่อนที่มันจะสามารถใช้มันได้เหมือนกันทั้งหมด ผู้เรียกต้องรู้จัก internals ด้วยTrackเช่นกันเนื่องจากมันต้องสร้างTrackวัตถุ แต่ผู้เรียกต้องรู้ข้อมูลนี้เหมือนกันทั้งหมดหากถูกส่งโดยตรงไปยังเมธอด กล่าวอีกนัยหนึ่งถ้าประโยชน์ของการห่อหุ้มไม่รู้จักเนื้อหาของวัตถุมันอาจไม่สามารถใช้ในกรณีนี้ได้เนื่องจากAlbumต้องใช้Trackข้อมูลของเหมือนกัน

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

ในการแก้ไขปัญหาคุณจะต้องแยกออกTrackเป็นส่วนต่อประสานและส่วนประกอบทางตรรกะเพื่อสร้างสองชั้นแยกจากกัน สำหรับผู้โทรให้Trackกลายเป็นคลาสไฟซึ่งหมายถึงการเก็บข้อมูลและเสนอการปรับให้เหมาะสมเล็กน้อย (ข้อมูลจากการคำนวณและ / หรือค่าเริ่มต้น) ภายในAlbumคุณจะใช้คลาสที่มีชื่อTrackDAOเพื่อทำการยกของหนักที่เกี่ยวข้องกับการบันทึกข้อมูลจากTrackไปยังฐานข้อมูล

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


3

ทั้งสองถูกต้อง

addTrack( Track t ) 

เป็นที่ดีกว่า (ตามที่คุณ argumented แล้ว) ในขณะที่

addTrack( int no, String title, double duration ) 

มีการเชื่อมโยงน้อยเนื่องจากโค้ดที่ใช้addTrackไม่จำเป็นต้องรู้ว่ามีTrackคลาส สามารถเปลี่ยนชื่อแทร็กได้โดยไม่จำเป็นต้องอัปเดตรหัสการโทร

ขณะที่คุณกำลังพูดคุยเกี่ยวกับการอ่านมากขึ้น / รหัสบำรุงรักษาบทความมีการพูดคุยเกี่ยวกับการมีเพศสัมพันธ์ รหัสคู่ที่น้อยลงนั้นไม่จำเป็นที่จะต้องนำไปใช้และเข้าใจได้ง่ายขึ้น


ดูอาร์กิวเมนต์ 4 ฉันไม่เห็นว่าคนที่สองนั้นมีคู่น้อยลงอย่างไร
m3th0dman

3

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

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

ก้าวต่อไปอีกขั้นหนึ่งหากแทร็กถูกคาดหวังว่าจะถูกแทนที่ด้วยบางสิ่งที่ทำงานได้ดีกว่าเดิมบางทีอินเทอร์เฟซที่กำหนดฟังก์ชันการทำงานที่จำเป็นต้องมีตามมาคือ ITrack ที่อาจอนุญาตให้มีการใช้งานที่แตกต่างกันเช่น "AnalogTrack", "CdTrack" และ "Mp3Track" ที่ให้ข้อมูลเพิ่มเติมที่เฉพาะเจาะจงมากขึ้นกับรูปแบบเหล่านั้นในขณะที่ยังคงให้ข้อมูลพื้นฐานของ ITrack ที่แสดงถึง "แทร็ค"; ชิ้นส่วนย่อยของเสียงที่ จำกัด แทร็กอาจเป็นคลาสพื้นฐานที่เป็นนามธรรม แต่สิ่งนี้ต้องการให้คุณต้องการใช้การนำไปใช้ใน Track เสมอ ปรับใช้ใหม่เป็น BetterTrack และตอนนี้คุณต้องเปลี่ยนพารามิเตอร์ที่คาดไว้

ดังนั้นกฎทอง โปรแกรมและส่วนประกอบของรหัสมักจะมีเหตุผลที่จะเปลี่ยนแปลง คุณไม่สามารถเขียนโปรแกรมที่ไม่ต้องใช้รหัสแก้ไขที่คุณเขียนไปแล้วเพื่อเพิ่มสิ่งใหม่หรือแก้ไขพฤติกรรมของมัน เป้าหมายของคุณในระเบียบวิธีใด ๆ (GRASP, SOLID ตัวย่อหรือคำศัพท์อื่น ๆ ที่คุณนึกถึง) ก็เพื่อระบุสิ่งที่จะต้องเปลี่ยนแปลงตลอดเวลาและออกแบบระบบเพื่อให้การเปลี่ยนแปลงนั้นง่ายที่สุดเท่าที่จะทำได้ (แปลแล้วให้สัมผัสกับโค้ดไม่กี่บรรทัดและส่งผลต่อพื้นที่อื่น ๆ ของระบบเกินขอบเขตการเปลี่ยนแปลงที่คุณตั้งใจไว้เท่าที่จะทำได้) ในกรณีที่สิ่งที่น่าจะมีการเปลี่ยนแปลงมากที่สุดคือแทร็กจะได้รับข้อมูลสมาชิกเพิ่มเติมที่ addTrack () อาจหรือไม่สนใจไม่ได้ แทร็กนั้นจะถูกแทนที่ด้วย BetterTrack

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.