วิธีการผูกมัดกับการห่อหุ้ม


17

มีปัญหา OOP แบบคลาสสิกของวิธีการผูกมัด vs วิธีการ "จุดเชื่อมต่อจุดเดียว":

main.getA().getB().getC().transmogrify(x, y)

VS

main.getA().transmogrifyMyC(x, y)

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

ข้อเสียของหลักสูตรคือการห่อหุ้มที่อ่อนแอซึ่งรหัสที่สองแก้ ตอนนี้ A มีการควบคุมทุกวิธีที่ผ่านมันและสามารถมอบมันให้กับเขตข้อมูลของมันถ้ามันต้องการ

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

คำตอบ:


25

ฉันคิดว่ากฎหมายของ Demeterให้แนวทางที่สำคัญในเรื่องนี้ (ด้วยข้อดีและข้อเสียของมันซึ่งตามปกติควรวัดในแต่ละกรณี)

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

ข้อเสียของกฎของ Demeter ก็คือบางครั้งมันต้องมีการเขียนวิธีการ "wrapper" ขนาดเล็กจำนวนมากเพื่อเผยแพร่วิธีการโทรไปยังส่วนประกอบ นอกจากนี้อินเทอร์เฟซของคลาสจะมีขนาดใหญ่เนื่องจากโฮสต์เมธอดสำหรับคลาสที่มีอยู่ส่งผลให้คลาสไม่มีอินเทอร์เฟซเหนียว แต่นี่อาจเป็นสัญญาณของการออกแบบที่ไม่ดี OO


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

@Oak ฉันเพิ่มใบเสนอราคาอธิบายถึงข้อดีและข้อเสีย
PéterTörök

10

โดยทั่วไปฉันพยายามทำให้วิธีการผูกมัดมี จำกัด มากที่สุดเท่าที่จะเป็นไปได้ (ตามกฎหมายของ Demeter )

ข้อยกเว้นเดียวที่ฉันทำได้คือสำหรับการเชื่อมต่อที่ราบรื่น / การเขียนโปรแกรมสไตล์ DSL ภายใน

Martin Fowler สร้างความแตกต่างในภาษาเฉพาะโดเมนแต่ด้วยเหตุผลของการแยกคำสั่งการละเมิดคำสั่งที่ระบุ:

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

ฟาวเลอร์ในหนังสือของเขาในหน้า 70 พูดว่า:

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


3

ฉันคิดว่าคำถามคือไม่ว่าคุณจะใช้นามธรรมที่เหมาะสม

ในกรณีแรกเรามี

interface IHasGetA {
    IHasGetB getA();
}

interface IHasGetB {
    IHasGetC getB();
}

interface IHasGetC {
    ITransmogrifyable getC();
}

interface ITransmogrifyable {
    void transmogrify(x,y);
}

IHasGetAที่ไหนหลักเป็นประเภท คำถามคือ: สิ่งที่เป็นนามธรรมเหมาะสมหรือไม่ คำตอบนั้นไม่สำคัญ และในกรณีนี้มันดูออกไปเล็กน้อย แต่มันก็เป็นตัวอย่างทางทฤษฎี แต่อย่างใด แต่เพื่อสร้างตัวอย่างที่แตกต่าง:

main.getA(v).getB(w).getC(x).transmogrify(y, z);

มักจะดีกว่า

main.superTransmogrify(v, w, x, y, z);

เพราะในตัวอย่างหลังทั้งสองthisและmainขึ้นอยู่กับประเภทของv, w, x, และy zนอกจากนี้รหัสไม่ได้ดูดีขึ้นมากจริงๆถ้าประกาศทุกวิธีมีข้อโต้แย้งครึ่งโหล

ตัวระบุตำแหน่งเซอร์วิสต้องใช้แนวทางแรกจริง ๆ คุณไม่ต้องการเข้าถึงอินสแตนซ์ที่สร้างขึ้นผ่านตัวระบุตำแหน่งบริการ

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

ตัวอย่างเช่นคุณอาจมี:

class Main implements IHasGetA, IHasGetA, IHasGetA, ITransmogrifyable {
    IHasGetB getA() { return this; }
    IHasGetC getB() { return this; }
    ITransmogrifyable getC() { return this; }
    void transmogrify(x,y) {
        return x + y;//yeah!
    }
}

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


จุดที่น่าสนใจมากเกี่ยวกับการเพิ่มขึ้นของจำนวนพารามิเตอร์
โอ๊ก

2

The Law of Demeterดังที่ @ PéterTörökชี้ให้เห็นว่ามีรูปแบบ "กะทัดรัด"

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


ในทางกลับกันสุ่มสี่สุ่มห้าตามกฎหมายหมายความว่าจำนวนวิธีการที่Aจะระเบิดด้วยวิธีการมากมายAอาจต้องการมอบหมายต่อไป แต่ถึงกระนั้นฉันเห็นด้วยกับการพึ่งพา - ที่จะลดจำนวนการพึ่งพาที่จำเป็นจากรหัสลูกค้า
โอ๊ก

1
@Oak: การทำสิ่งที่สุ่มสี่สุ่มห้าไม่เคยดี เราต้องมองถึงข้อดีข้อเสียและการตัดสินใจบนพื้นฐานของหลักฐาน ซึ่งรวมถึงกฎหมายของ Demeter เช่นกัน
CesarGon

2

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

ในทางกลับกันการมีคลาสที่เพียงแค่ "ส่งต่อ" วิธีการยังหมายถึงค่าใช้จ่ายในการประกาศหลายวิธีในที่เดียว

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


1

ฉันมักจะพบว่าตรรกะของโปรแกรมนั้นง่ายต่อการติดตามด้วยวิธีการที่ถูกล่ามโซ่ สำหรับฉันแล้วcustomer.getLastInvoice().itemCount()พอดีกับสมองของฉันดีกว่าcustomer.countLastInvoiceItems()ควรเป็นสมองของฉันดีกว่า

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


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