เมื่อใดจะไปได้อย่างคล่องแคล่วใน C #?


78

ในหลาย ๆ แง่มุมฉันชอบความคิดของอินเทอร์เฟซ Fluent แต่ด้วยคุณลักษณะที่ทันสมัยทั้งหมดของ C # (initializers, lambdas, พารามิเตอร์ที่มีชื่อ) ฉันคิดว่าตัวเองคิดว่า "มันคุ้มค่าหรือไม่" และ "นี่เป็นรูปแบบที่เหมาะสมหรือไม่ ใช้?". ใครช่วยฉันได้บ้างถ้าไม่ใช่วิธีปฏิบัติที่ได้รับการยอมรับอย่างน้อยประสบการณ์ของพวกเขาหรือเมทริกซ์การตัดสินใจว่าจะใช้รูปแบบ Fluent ได้เมื่อใด

สรุป:

กฎง่ายๆที่ดีจากคำตอบจนถึงตอนนี้:

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

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

Employees.CreateNew().WithFirstName("Peter")
                     .WithLastName("Gibbons")
                     .WithManager()
                          .WithFirstName("Bill")
                          .WithLastName("Lumbergh")
                          .WithTitle("Manager")
                          .WithDepartment("Y2K");

สามารถเขียนด้วย initializers ได้อย่างง่ายดายเช่น:

Employees.Add(new Employee()
              {
                  FirstName = "Peter",
                  LastName = "Gibbons",
                  Manager = new Employee()
                            {
                                 FirstName = "Bill",
                                 LastName = "Lumbergh",
                                 Title = "Manager",
                                 Department = "Y2K"
                            }
              });

ฉันยังสามารถใช้พารามิเตอร์ที่มีชื่อในตัวสร้างในตัวอย่างนี้


1
เป็นคำถามที่ดี แต่ฉันคิดว่ามันเป็นคำถามที่มากกว่าวิกิพีเดีย
Ivo

คำถามของคุณถูกแท็ก "fluent-nhibernate" คุณกำลังพยายามตัดสินใจว่าจะสร้างส่วนต่อประสานที่คล่องแคล่วหรือว่าจะใช้การตั้งค่า nhibernate กับ XML ได้อย่างคล่องแคล่วหรือไม่
Ilya Kogan

1
โหวตให้โยกย้ายไปยัง Programmers.SE
Matt Ellen

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

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

คำตอบ:


28

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

กล่าวอีกนัยหนึ่งถ้ารหัสของคุณอ่านมากกว่าที่เขียน (และรหัสอะไรไม่ได้) คุณควรพิจารณาสร้างส่วนต่อประสานที่คล่องแคล่ว

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

  1. บริบท (ดังนั้นเมื่อคุณมักจะดำเนินการหลายอย่างตามลำดับด้วยสิ่งเดียวกันคุณสามารถโยงการดำเนินการโดยไม่ต้องประกาศบริบทของคุณซ้ำแล้วซ้ำอีก)
  2. การค้นพบ (เมื่อคุณไปobjectA.แล้ว IntelliSense ช่วยให้คุณมากมายของคำแนะนำ. ในกรณีของฉันข้างต้นplm.Led.จะช่วยให้คุณตัวเลือกทั้งหมดสำหรับการควบคุมในตัวไฟ LED และplm.Network.ช่วยให้คุณมีสิ่งที่คุณสามารถทำได้ด้วยอินเตอร์เฟซเครือข่าย. plm.Network.X10.จะช่วยให้คุณย่อยของ การกระทำของเครือข่ายสำหรับอุปกรณ์ X10 คุณจะไม่ได้รับสิ่งนี้ด้วยตัวเริ่มต้นของตัวสร้าง (เว้นแต่ว่าคุณต้องการสร้างวัตถุสำหรับการกระทำประเภทต่าง ๆ ทุกประเภทซึ่งไม่ใช่ลักษณะเฉพาะ)
  3. Reflection (ไม่ได้ใช้ในตัวอย่างด้านบน) - ความสามารถในการส่งผ่านในการแสดงออกของ LINQ และจัดการมันเป็นเครื่องมือที่มีประสิทธิภาพมากโดยเฉพาะอย่างยิ่งใน API ตัวช่วยบางอย่างที่ฉันสร้างขึ้นสำหรับการทดสอบหน่วย ฉันสามารถส่งผ่านการแสดงออกในทรัพย์สินทะเยอทะยานสร้างการแสดงออกที่มีประโยชน์ทั้งกลุ่มรวบรวมและเรียกใช้เหล่านั้นหรือแม้กระทั่งใช้ทรัพย์สินทะเยอทะยานเพื่อตั้งค่าบริบทของฉัน

สิ่งหนึ่งที่ฉันมักจะทำคือ:

test.Property(t => t.SomeProperty)
    .InitializedTo(string.Empty)
    .CantBeNull() // tries to set to null and Asserts ArgumentNullException
    .YaddaYadda();

ฉันไม่เห็นว่าคุณสามารถทำสิ่งนี้ได้อย่างไรโดยไม่มีส่วนต่อประสานที่คล่องแคล่ว

แก้ไข 2 : คุณสามารถทำการปรับปรุงการอ่านที่น่าสนใจเช่น:

test.ListProperty(t => t.MyList)
    .ShouldHave(18).Items()
    .AndThenAfter(t => testAddingItemToList(t))
    .ShouldHave(19).Items();

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

ขอบคุณสำหรับการตอบกลับแบบขยาย ฉันคิดว่าคุณได้ระบุกฎง่ายๆสองข้อไว้ดังนี้: 1) ใช้ความคล่องแคล่วเมื่อคุณมีการโทรจำนวนมากซึ่งได้รับประโยชน์จากบริบท 'ส่งผ่าน' ของบริบท 2) คิดอย่างคล่องแคล่วเมื่อคุณมีสายมากกว่าตัวตั้งค่า
Andrew Hanlon

2
@ ถึงฉันไม่เห็นอะไรเลยในการตอบกลับเกี่ยวกับ "การโทรมากกว่า setters" คุณสับสนกับคำพูดของเขาเกี่ยวกับ "code [ที่] อ่านมากกว่าที่เขียน" หรือไม่? ไม่เกี่ยวกับตัวรับทรัพย์สิน / ผู้ตั้งค่ามันเป็นเรื่องของมนุษย์ที่อ่านรหัสกับมนุษย์ที่เขียนรหัส - เกี่ยวกับการทำให้โค้ดอ่านง่ายสำหรับมนุษย์ที่จะอ่านเพราะโดยทั่วไปแล้วเราจะอ่านบรรทัดของรหัสที่ให้บ่อยกว่าที่เราแก้ไข
Joe White

@ Joe White ฉันควรจะใช้ถ้อยคำใหม่ 'call' to 'action' แต่ความคิดก็ยังคงอยู่ ขอบคุณ
Andrew Hanlon

การสะท้อนกลับสำหรับการทดสอบนั้นชั่วร้าย!
Adronius

24

Scott Hanselman พูดถึงเรื่องนี้ในEpisode 260 จากพอดคาสต์ของเขา Hanselminutesกับ Jonathan Carter พวกเขาอธิบายว่าอินเทอร์เฟซที่คล่องแคล่วเหมือน UI บน API คุณไม่ควรให้อินเทอร์เฟซที่คล่องแคล่วเป็นจุดเข้าใช้งานเพียงอย่างเดียว แต่ให้เป็นอินเทอร์เฟซสำหรับโค้ดบางส่วนที่ด้านบนของ "อินเทอร์เฟซ API ปกติ"

โจนาธานคาร์เตอร์ยังพูดเล็กน้อยเกี่ยวกับการออกแบบ API ในบล็อกของเขา


ขอบคุณมากสำหรับลิงก์ข้อมูลและ UI ที่อยู่ด้านบนของ API เป็นวิธีที่ดีในการดู
Andrew Hanlon

14

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

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

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

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


ขอบคุณสำหรับการตอบกลับมันไม่สายเกินไปที่จะให้คำตอบที่ดี
Andrew Hanlon

ฉันไม่รังเกียจคำนำหน้า 'พร้อม' สำหรับวิธีการ มันทำให้พวกเขาแตกต่างจากวิธีอื่น ๆ ที่ไม่ส่งคืนวัตถุสำหรับการผูกมัด เช่นposition.withX(5)เมื่อเทียบกับposition.getDistanceToOrigin()
LegendLength

5

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

Employees.CreateNew().WithFirstName("Peter")…

สามารถเขียนด้วย initializers ได้อย่างง่ายดายเช่น:

Employees.Add(new Employee() { FirstName = "Peter",  });

ในสายตาของฉันทั้งสองรุ่นควรจะหมายถึงและทำสิ่งที่แตกต่าง

  • ซึ่งแตกต่างจากรุ่นที่ไม่ใช่ภาษาอย่างคล่องแคล่ว, รุ่นได้อย่างคล่องแคล่วซ่อนความจริงที่ว่าใหม่Employeeนี้ยังรวมAddถึงการเก็บรวบรวมEmployees- มันเพียงแสดงให้เห็นว่าวัตถุใหม่คือCreated

  • ความหมายของการ….WithX(…)เป็นคลุมเครือโดยเฉพาะอย่างยิ่งสำหรับคนที่มาจาก F # ซึ่งมีwithคำหลักสำหรับการแสดงออกวัตถุ : พวกเขาอาจจะตีความobj.WithX(x)เป็นใหม่วัตถุถูกรับมาจากobjที่เหมือนกับobjยกเว้นของทรัพย์สินที่มีค่าX xในทางตรงกันข้ามกับรุ่นที่สองมันชัดเจนว่าไม่มีวัตถุที่ได้รับการสร้างขึ้นและคุณสมบัติทั้งหมดที่ระบุไว้สำหรับวัตถุต้นฉบับ

….WithManager().With
  • สิ่งนี้….With…มีความหมายอื่น: สลับ "โฟกัส" ของการเริ่มต้นคุณสมบัติเป็นวัตถุอื่น ข้อเท็จจริงที่ว่า API ที่คล่องแคล่วของคุณมีความหมายต่างกันสองประการWithคือทำให้ยากที่จะตีความสิ่งที่เกิดขึ้นได้อย่างถูกต้อง ... ซึ่งอาจเป็นสาเหตุที่คุณใช้การเยื้องในตัวอย่างของคุณเพื่อแสดงให้เห็นถึงความหมายที่ต้องการของรหัสนั้น มันจะชัดเจนขึ้นเช่นนี้

    (employee).WithManager(Managers.CreateNew().WithFirstName("Bill").…)
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //                     value of the `Manager` property appears inside the parentheses,
    //                     like with `WithName`, where the `Name` is also inside the parentheses 
    

สรุป: "ซ่อน" คุณสมบัติภาษาที่ง่ายพอ ๆnew T { X = x }กับ API ได้อย่างคล่องแคล่ว ( Ts.CreateNew().WithX(x)) สามารถทำได้อย่างชัดเจน แต่:

  1. จะต้องระมัดระวังอย่างยิ่งว่าผู้อ่านของรหัสที่ได้อย่างคล่องแคล่วยังคงเข้าใจสิ่งที่มันทำ นั่นคือ API ที่คล่องแคล่วควรมีความโปร่งใสในความหมายและชัดเจน การออกแบบ API ดังกล่าวอาจทำงานได้ดีกว่าที่คาดการณ์ไว้ (อาจต้องมีการทดสอบเพื่อความสะดวกในการใช้งานและการยอมรับ) และ / หรือ ...

  2. การออกแบบอาจใช้งานได้มากกว่าที่จำเป็น: ในตัวอย่างนี้ API ที่คล่องแคล่วเพิ่ม "ความสะดวกสบายของผู้ใช้" น้อยกว่า API พื้นฐาน (คุณลักษณะภาษา) อาจกล่าวได้ว่า API ที่คล่องแคล่วควรทำให้คุณลักษณะ API / ภาษาพื้นฐาน "ใช้งานง่ายขึ้น"; นั่นคือมันควรบันทึกโปรแกรมเมอร์เป็นจำนวนมากของความพยายาม ถ้ามันเป็นอีกวิธีหนึ่งในการเขียนสิ่งเดียวกันมันอาจจะไม่คุ้มค่าเพราะมันไม่ได้ทำให้ชีวิตโปรแกรมเมอร์ง่ายขึ้น แต่ทำให้นักออกแบบทำงานหนักขึ้น (ดูข้อสรุปที่ 1 ด้านบน)

  3. ทั้งสองจุดข้างบนเงียบ ๆ สมมติว่า API ได้อย่างคล่องแคล่วเป็นเลเยอร์เหนือ API หรือคุณลักษณะภาษาที่มีอยู่ สมมติฐานนี้อาจเป็นแนวทางที่ดีอีกประการหนึ่ง: API ที่คล่องแคล่วอาจเป็นวิธีพิเศษในการทำบางสิ่งไม่ใช่วิธีเดียว นั่นคืออาจเป็นความคิดที่ดีที่จะเสนอ API ที่คล่องแคล่วเป็นตัวเลือก "เลือกเข้าร่วม"


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

2

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

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


ขอบคุณสำหรับการตอบกลับของคุณฉันคิดว่าคุณได้แสดงกฎที่ดี: คล่องแคล่วดีกว่าด้วยการโทรจำนวนมากผ่านผู้ตั้งค่าหลายคน
Andrew Hanlon

1

ฉันไม่คุ้นเคยกับคำศัพท์ที่ใช้งานได้คล่องแต่มันทำให้ฉันนึกถึง API ที่ฉันเคยใช้ซึ่งรวมถึงLINQด้วย

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

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


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