เมื่อไรที่เมธอดของคลาสส่งคืนอินสแตนซ์เดียวกันหลังจากแก้ไขตัวเอง?


9

ฉันได้เรียนที่มีสามวิธีA(), และB() C()วิธีการเหล่านั้นแก้ไขอินสแตนซ์ของตัวเอง

ในขณะที่วิธีการจะต้องส่งคืนอินสแตนซ์เมื่ออินสแตนซ์เป็นสำเนาแยกต่างหาก (เช่นเดียวกับClone()) ฉันมีตัวเลือกฟรีที่จะส่งคืนvoidหรืออินสแตนซ์เดียวกัน ( return this;) เมื่อทำการแก้ไขอินสแตนซ์เดียวกันในวิธีการและไม่คืนค่าอื่น ๆ

obj.A().B().C();เมื่อตัดสินใจกลับมาเช่นการแก้ไขเดียวกันผมสามารถทำโซ่วิธีการเรียบร้อยเช่น

นี่จะเป็นเหตุผลเดียวที่ทำเช่นนั้น?

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

คำตอบ:


7

เมื่อใดควรใช้การผูกมัด

ฟังก์ชั่นผูกมัดส่วนใหญ่เป็นที่นิยมในภาษาที่ IDE กับการเติมอัตโนมัติเป็นเรื่องธรรมดา ตัวอย่างเช่นนักพัฒนา C # เกือบทั้งหมดใช้ Visual Studio ดังนั้นหากคุณกำลังพัฒนาด้วยการเพิ่มการผูกมัด C # กับวิธีการของคุณอาจเป็นการประหยัดเวลาสำหรับผู้ใช้ของคลาสนั้นเพราะ Visual Studio จะช่วยคุณในการสร้างห่วงโซ่

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

ผูกมัดคืออะไร?

ให้ชั้นชื่อFooสองวิธีต่อไปนี้เป็นทั้ง chainable

function what() { return this; }
function when() { return new Foo(this); }

ความจริงที่ว่าหนึ่งคือการอ้างอิงถึงอินสแตนซ์ปัจจุบันและเป็นหนึ่งในการสร้างอินสแตนซ์ใหม่จะไม่เปลี่ยนแปลงว่าสิ่งเหล่านี้เป็นวิธีการที่ chainable

ไม่มีกฎทองคำที่วิธีการเชื่อมโยงจะต้องอ้างอิงวัตถุปัจจุบันเท่านั้น Infact วิธีการเชื่อมต่อสามารถข้ามชั้นเรียนได้สองแบบ ตัวอย่างเช่น;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

ไม่มีการอ้างอิงถึงthisในตัวอย่างใด ๆ ข้างต้น รหัสa.What().When()เป็นตัวอย่างของการผูกมัด สิ่งที่น่าสนใจคือประเภทของคลาสBไม่เคยถูกกำหนดให้กับตัวแปร

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

นี่คือตัวอย่างเพิ่มเติม

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

ควรใช้เมื่อใดthisและnew(this)

เงื่อนไขในภาษาส่วนใหญ่ไม่เปลี่ยนรูป ดังนั้นวิธีการผูกมัดการโทรมักจะส่งผลให้เกิดสายใหม่ที่ถูกสร้างขึ้น ในกรณีที่วัตถุเช่น StringBuilder สามารถแก้ไขได้

ความสอดคล้องเป็นแนวปฏิบัติที่ดีที่สุด

หากคุณมีวิธีการที่แก้ไขสถานะของวัตถุและส่งคืนthisอย่ารวมวิธีการที่ส่งคืนอินสแตนซ์ใหม่ ให้สร้างวิธีการเฉพาะที่เรียกClone()ว่าจะทำสิ่งนี้แทน

 var x  = a.Foo().Boo().Clone().Foo();

aนั่นเป็นจำนวนมากที่ชัดเจนเป็นสิ่งที่เกิดขึ้นภายใน

เคล็ดลับขั้นตอนนอกและด้านหลัง

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

คลาสชั่วคราวมีไว้เพื่อมอบคุณสมบัติพิเศษให้กับคลาสดั้งเดิม แต่ภายใต้เงื่อนไขพิเศษเท่านั้น

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

Bนี่คือตัวอย่างของฉันให้รัฐพิเศษที่รู้จักในฐานะ

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

ตอนนี้เป็นตัวอย่างที่ง่ายมาก หากคุณต้องการมีการควบคุมที่มากขึ้นคุณBสามารถกลับไปใช้ super-type ใหม่Aที่มีวิธีการต่างกัน


4
ฉันไม่เข้าใจว่าทำไมคุณจึงแนะนำวิธีการผูกมัดขึ้นอยู่กับการสนับสนุนการเติมข้อความอัตโนมัติเหมือน Intellisense แต่เพียงผู้เดียว เชนเมธอดหรืออินเตอร์เฟสที่คล่องแคล่วเป็นรูปแบบการออกแบบ API สำหรับภาษา OOP ใด ๆ สิ่งเดียวที่การเติมข้อความอัตโนมัติไม่ป้องกันการพิมพ์ผิดและข้อผิดพลาดประเภท
amon

@ คุณเข้าใจผิดในสิ่งที่ฉันพูด ฉันบอกว่ามันเป็นที่นิยมมากขึ้นเมื่อ intellisense เป็นเรื่องปกติสำหรับภาษา ฉันไม่เคยบอกว่ามันขึ้นอยู่กับคุณสมบัติ
ปฏิกิริยา

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

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

3

วิธีการเหล่านั้นแก้ไขอินสแตนซ์ของตัวเอง

ขึ้นอยู่กับภาษาการมีเมธอดที่ส่งคืนvoid/ unitและแก้ไขอินสแตนซ์หรือพารามิเตอร์ของพวกมันนั้นไม่ใช่แบบ แม้ในภาษาที่เคยทำมากกว่านั้น (C #, C ++) มันกำลังจะล้าสมัยด้วยการเปลี่ยนไปสู่การเขียนโปรแกรมสไตล์การใช้งานที่มากขึ้น (วัตถุที่ไม่เปลี่ยนรูป, ฟังก์ชันบริสุทธิ์) แต่สมมติว่าตอนนี้มีเหตุผลที่ดี

นี่จะเป็นเหตุผลเดียวที่ทำเช่นนั้น?

สำหรับพฤติกรรมบางอย่าง (คิดว่าx++) คาดว่าการดำเนินการจะส่งคืนผลลัพธ์แม้ว่าจะแก้ไขตัวแปร แต่นั่นเป็นเพียงเหตุผลเดียวที่ทำด้วยตัวเอง

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

มันขึ้นอยู่กับ.

ในภาษาที่การคัดลอก / ใหม่และการส่งคืนเป็นเรื่องปกติ (C # LINQ และสตริง) การส่งคืนการอ้างอิงเดียวกันจะทำให้เกิดความสับสน ในภาษาที่การแก้ไขและส่งคืนเป็นเรื่องปกติ (บางไลบรารี C ++) การคัดลอกจะทำให้เกิดความสับสน

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


ขอบคุณสำหรับคำตอบของคุณ ฉันทำงานเป็นส่วนใหญ่กับ. NET Framework และโดยคำตอบของคุณมันเต็มไปด้วยสิ่งที่ไม่ใช่สำนวน ในขณะที่สิ่งที่ต้องการวิธีการ LINQ กลับสำเนาใหม่ของเดิมหลังจากที่ผนวกการดำเนินงานอื่น ๆ สิ่งที่ต้องการClear()หรือAdd()ประเภทคอลเลกชันใด ๆ voidที่จะปรับเปลี่ยนเช่นเดียวกันและผลตอบแทน ในทั้งสองกรณีคุณพูดว่ามันจะทำให้งง ...
มาร์ตินเบราน์

@modix - LINQ ไม่ส่งคืนสำเนาขณะที่ผนวกการดำเนินการ
Telastyn

ทำไมฉันถึงทำอย่างobj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);นั้น? Where()ส่งคืนวัตถุคอลเลกชันใหม่ OrderBy()แม้จะส่งคืนวัตถุคอลเลกชันที่แตกต่างกันเพราะเนื้อหามีการสั่งซื้อ
Martin Braun

1
@modix - พวกเขากลับวัตถุใหม่ที่ใช้IEnumerableแต่ต้องมีความชัดเจนพวกเขาจะไม่สำเนาและพวกเขาไม่ได้เป็นคอลเลกชัน (ยกเว้นToList, ToArrayฯลฯ )
Telastyn

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

0

(ฉันถือว่า C ++ เป็นภาษาโปรแกรมของคุณ)

สำหรับฉันนี่เป็นส่วนใหญ่อ่านง่าย ถ้า A, B, C เป็นตัวดัดแปลงโดยเฉพาะอย่างยิ่งถ้านี่เป็นวัตถุชั่วคราวที่ส่งผ่านเป็นพารามิเตอร์ให้กับบางฟังก์ชั่นเช่น

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

เปรียบเทียบกับ

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

การลงทะเบียนใหม่ถ้ามันเป็นการตกลงที่จะส่งคืนการอ้างอิงไปยังอินสแตนซ์ที่ได้รับการแก้ไขฉันจะบอกว่าใช่และชี้ให้คุณเป็นตัวอย่างให้กับตัวดำเนินการสตรีม '>>' และ '<<' ( http://www.cprogramming.com/tutorial/ operator_overloading.html )


1
คุณถือว่าผิดมันเป็น C # อย่างไรก็ตามฉันต้องการให้คำถามของฉันเป็นเรื่องธรรมดามากขึ้น
Martin Braun

แน่นอนว่าอาจเป็น Java ด้วย แต่นั่นก็เป็นเพียงจุดเล็ก ๆ น้อย ๆ ที่จะวางตัวอย่างของฉันกับตัวดำเนินการในบริบท สาระสำคัญคือมันทำให้ความสามารถในการอ่านลดลงซึ่งได้รับอิทธิพลจากความคาดหวังและภูมิหลังของผู้อ่านซึ่งตัวเองได้รับอิทธิพลจากการปฏิบัติตามปกติในภาษา / กรอบงานหนึ่งที่เกี่ยวข้องกับ - ตามคำตอบอื่น ๆ
AdrianI

0

นอกจากนี้คุณยังสามารถทำสิ่งที่ผูกมัดกับวิธีการคัดลอกผลตอบแทนมากกว่าแก้ไขผลตอบแทน

ตัวอย่าง C # ที่ดีคือ string แทนที่ (a, b) ซึ่งจะไม่เปลี่ยนสตริงที่ถูกเรียกใช้ แต่จะส่งคืนสตริงใหม่แทนที่จะอนุญาตให้คุณโยงไปอย่างสนุกสนาน


ใช่ฉันรู้. แต่ฉันไม่ต้องการอ้างถึงกรณีนี้เนื่องจากฉันต้องใช้เคสนี้เมื่อฉันไม่ต้องการแก้ไขอินสแตนซ์ของตัวเองอย่างไรก็ตาม อย่างไรก็ตามขอบคุณสำหรับการชี้ให้เห็นว่า
Martin Braun

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