ทำความสะอาดรหัสและวัตถุไฮบริดและความอิจฉาของคุณสมบัติ


10

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

ผสมผสาน

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

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

เมื่อเร็ว ๆ นี้ฉันกำลังดูรหัสไปยังหนึ่งในวัตถุงานของฉัน (ที่เกิดขึ้นกับรูปแบบผู้เข้าชม ) และเห็นสิ่งนี้:

@Override
public void visit(MarketTrade trade) {
    this.data.handleTrade(trade);
    updateRun(trade);
}

private void updateRun(MarketTrade newTrade) {
    if(this.data.getLastAggressor() != newTrade.getAggressor()) {
        this.data.setRunLength(0);
        this.data.setLastAggressor(newTrade.getAggressor());
    }
    this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}

ฉันพูดกับตัวเองทันที "อิจฉาคุณลักษณะ! ตรรกะนี้ควรอยู่ในDataชั้นเรียน - โดยเฉพาะในhandleTradeวิธีการhandleTradeและupdateRunควรเกิดขึ้นด้วยกันเสมอ " แต่ฉันคิดว่า "คลาสข้อมูลเป็นเพียงpublicโครงสร้างข้อมูลถ้าฉันเริ่มทำสิ่งนั้นมันจะกลายเป็นวัตถุไฮบริด!"

มีอะไรดีกว่าแล้วทำไม คุณตัดสินใจว่าจะทำอย่างไร


2
ทำไม 'ข้อมูล' จึงต้องเป็นโครงสร้างข้อมูลเลย มันมีพฤติกรรมที่ชัดเจน ดังนั้นเพียงแค่ปิด getters และ setters ทั้งหมดเพื่อไม่ให้วัตถุสามารถควบคุมสถานะภายในได้
Cormac Mulhall

คำตอบ:


9

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

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

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

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

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

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

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


5

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

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

รหัสเก่า:

MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();

รหัสใหม่:

MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();

นี่มีข้อดีหลายประการ:

  • มันย้ายข้อกังวลแยกเป็นวัตถุแยกต่างหาก ( SRP )
  • มันลบการมีเพศสัมพันธ์ชั่วคราว: มันเป็นไปไม่ได้ที่จะเรียกวิธีการที่ไม่เป็นระเบียบและลายเซ็นวิธีการให้เอกสารโดยนัยเกี่ยวกับวิธีการเรียกพวกเขา คุณเคยดูเอกสารเห็นวัตถุที่คุณต้องการและทำงานย้อนหลังหรือไม่? ฉันต้องการวัตถุ Z แต่ฉันต้องการ Y เพื่อให้ได้ Z เพื่อให้ได้ Y ฉันต้องการ X. Aha! ฉันมี W ซึ่งจำเป็นต้องมี X. Chain เข้าด้วยกันและ W ของคุณสามารถใช้เพื่อรับ Z ได้
  • การแยกวัตถุเช่นนี้มีแนวโน้มที่จะทำให้พวกมันไม่เปลี่ยนรูปซึ่งมีข้อได้เปรียบมากมายนอกเหนือจากขอบเขตของคำถามนี้ Takeaway ที่รวดเร็วคือวัตถุที่ไม่เปลี่ยนรูปมักจะนำไปสู่รหัสที่ปลอดภัยกว่า

ไม่มีการเชื่อมต่อทางโลกระหว่างสองวิธีนี้เรียก สลับลำดับของพวกเขาและพฤติกรรมจะไม่เปลี่ยนแปลง
durron597

1
ผมเริ่มคิดว่ายังลำดับ / ชั่วขณะมีเพศสัมพันธ์เมื่ออ่านคำถาม แต่แล้วสังเกตเห็นว่าupdateRunวิธีการที่เป็นส่วนตัว คำแนะนำที่ดีหลีกเลี่ยงการมีเพศสัมพันธ์แบบต่อเนื่อง แต่จะใช้กับการออกแบบ API / ส่วนต่อประสานสาธารณะเท่านั้นและไม่ใช้กับรายละเอียดการใช้งาน คำถามจริงน่าจะเป็นว่าupdateRunควรจะอยู่ในผู้เข้าชมหรือในชั้นข้อมูลและฉันไม่เห็นว่าคำตอบนี้ตอบปัญหานั้นได้อย่างไร
amon

การมองเห็นของupdateRunไม่เกี่ยวข้องสิ่งที่สำคัญคือการดำเนินการthis.dataที่ไม่ปรากฏในคำถามและเป็นวัตถุที่ถูกจัดการโดยวัตถุผู้เข้าชม

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

0

จากมุมมองของฉันชั้นควรมี "ค่าสำหรับรัฐ (ตัวแปรสมาชิก) และการใช้งานของพฤติกรรม (ฟังก์ชั่นสมาชิกวิธีการ)"

"โครงสร้างข้อมูลแบบไฮบริดที่โชคร้าย" จะปรากฏขึ้นหากคุณกำหนดให้สมาชิกตัวแปรระดับรัฐ (หรือผู้ได้รับ / ผู้ตั้งค่า) เป็นสาธารณะซึ่งไม่ควรเปิดเผยต่อสาธารณะ

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

คุณควรจะเก็บสถานะสมาชิกตัวแปรที่ไม่ใช่สาธารณะ (ชั้นฐานข้อมูลของคุณควรสามารถจัดการตัวแปรสมาชิกที่ไม่ใช่สาธารณะ)

Feature Envy เป็นคลาสที่ใช้วิธีการของคลาสอื่นมากเกินไป ดูCode_smell การมีชั้นเรียนที่มีวิธีการและสถานะจะกำจัดสิ่งนี้

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