Khi nào thành thạo C #?


78

Ở nhiều khía cạnh, tôi thực sự thích ý tưởng về giao diện Fluent, nhưng với tất cả các tính năng hiện đại của C # (bộ khởi tạo, lambdas, tham số có tên) Tôi thấy mình đang nghĩ, "nó có đáng không?", Và "Đây có phải là mô hình phù hợp không sử dụng?". Bất cứ ai cũng có thể cho tôi, nếu không phải là một thực tiễn được chấp nhận, ít nhất là kinh nghiệm hoặc ma trận quyết định của riêng họ khi nào nên sử dụng mẫu Fluent?

Phần kết luận:

Một số quy tắc tốt từ các câu trả lời cho đến nay:

  • Giao diện trôi chảy giúp ích rất nhiều khi bạn có nhiều hành động hơn setters, vì các cuộc gọi được hưởng lợi nhiều hơn từ việc truyền qua ngữ cảnh.
  • Giao diện lưu loát nên được coi là một lớp trên đầu của một api, không phải là phương tiện sử dụng duy nhất.
  • Các tính năng hiện đại như lambdas, bộ khởi tạo và các tham số được đặt tên, có thể hoạt động cùng nhau để làm cho giao diện trôi chảy thậm chí còn thân thiện hơn.

Dưới đây là một ví dụ về những gì tôi muốn nói bởi các tính năng hiện đại làm cho nó cảm thấy ít cần thiết hơn. Lấy ví dụ một giao diện (có lẽ là kém) cho phép tôi tạo một Nhân viên như:

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

Có thể dễ dàng được viết với các trình khởi tạo như:

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

Tôi cũng có thể đã sử dụng các tham số được đặt tên trong các hàm tạo trong ví dụ này.


1
Câu hỏi hay, nhưng tôi nghĩ nó nhiều hơn một câu hỏi wiki
Ivo

Câu hỏi của bạn được gắn thẻ "thông thạo-nhibernate". Vì vậy, bạn đang cố gắng quyết định có nên tạo một giao diện trôi chảy hay không, hay sử dụng cấu hình thông thạo so với XML?
Ilya Kogan

1
Được bầu chọn để di chuyển đến các lập trình viên.SE
Matt Ellen

@Ilya Kogan, tôi nghĩ rằng nó thực sự được gắn thẻ "giao diện lưu loát" là một thẻ chung cho mẫu giao diện trôi chảy. Câu hỏi này không liên quan đến nhibernate, mà như bạn đã nói chỉ nên tạo giao diện trôi chảy. Cảm ơn.

1
Bài đăng này đã truyền cảm hứng cho tôi để nghĩ ra cách sử dụng mẫu này trong C. Nỗ lực của tôi có thể được tìm thấy tại trang web chị em Code Review .
otto

Câu trả lời:


28

Viết một giao diện thông thạo (tôi đã dabbled với nó) có nỗ lực nhiều hơn, nhưng nó vẫn có một lương-off bởi vì nếu bạn làm điều đó đúng, mục đích của các kết quả sử dụng code đang rõ ràng hơn. Nó thực chất là một dạng ngôn ngữ cụ thể của miền.

Nói cách khác, nếu mã của bạn được đọc nhiều hơn so với mã được viết (và mã nào không?), Thì bạn nên xem xét việc tạo giao diện trôi chảy.

Giao diện trôi chảy liên quan nhiều đến bối cảnh và không chỉ là cách để cấu hình các đối tượng. Như bạn có thể thấy trong liên kết ở trên, tôi đã sử dụng API thông thạo để đạt được:

  1. Bối cảnh (vì vậy khi bạn thường thực hiện nhiều hành động theo trình tự với cùng một thứ, bạn có thể xâu chuỗi các hành động mà không phải khai báo ngữ cảnh của mình nhiều lần).
  2. Khả năng khám phá (khi bạn đi đến objectA.sau đó IntelliSense cung cấp cho bạn rất nhiều gợi ý. Trong trường hợp của tôi ở trên, plm.Led.mang đến cho bạn tất cả các tùy chọn cho việc kiểm soát các built-in đèn LED, và plm.Network.mang đến cho bạn những điều bạn có thể làm với các giao diện mạng. plm.Network.X10.Cung cấp cho bạn các tập hợp con của hành động mạng cho các thiết bị X10. Bạn sẽ không nhận được điều này với trình khởi tạo hàm tạo (trừ khi bạn muốn phải xây dựng một đối tượng cho mọi loại hành động khác nhau, không phải là thành ngữ).
  3. Reflection (không được sử dụng trong ví dụ ở trên) - khả năng vượt qua biểu thức LINQ và thao tác nó là một công cụ rất mạnh, đặc biệt là trong một số API của trình trợ giúp tôi đã xây dựng cho các bài kiểm tra đơn vị. Tôi có thể chuyển vào một biểu thức getter thuộc tính, xây dựng một loạt các biểu thức hữu ích, biên dịch và chạy chúng, hoặc thậm chí sử dụng getter thuộc tính để thiết lập bối cảnh của tôi.

Một điều tôi thường làm là:

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

Tôi không thấy làm thế nào bạn có thể làm một cái gì đó như thế mà không có giao diện trôi chảy.

Chỉnh sửa 2 : Bạn cũng có thể thực hiện các cải tiến khả năng đọc thực sự thú vị, như:

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

Cảm ơn bạn đã trả lời, tuy nhiên tôi biết lý do sử dụng Fluent, nhưng tôi đang tìm một lý do cụ thể hơn để sử dụng nó trên một cái gì đó giống như ví dụ mới của tôi ở trên.
Andrew Hanlon

Cảm ơn đã trả lời mở rộng. Tôi nghĩ rằng bạn đã vạch ra hai quy tắc tốt: 1) Sử dụng thành thạo khi bạn có nhiều cuộc gọi được hưởng lợi từ 'truyền qua' ngữ cảnh. 2) Nghĩ về Fluent khi bạn có nhiều cuộc gọi hơn setters.
Andrew Hanlon

2
@ach, tôi không thấy bất cứ điều gì trong bài trả lời này về "nhiều cuộc gọi hơn setters". Bạn có bối rối trước câu nói của anh ấy về "mã [mà] được đọc nhiều hơn so với nó được viết" không? Đó không phải là về getters / setters tài sản, mà là về con người đọc mã so với con người viết mã - về việc làm cho mã dễ đọc cho con người , bởi vì chúng ta thường đọc một dòng mã nhất định thường xuyên hơn chúng ta sửa đổi nó.
Joe White

@Joe White, có lẽ tôi nên viết lại cụm từ 'gọi' thành 'hành động' của mình. Nhưng ý tưởng vẫn đứng vững. Cảm ơn.
Andrew Hanlon

Phản ánh để thử nghiệm là xấu xa!
Adronius

24

Scott Hanselman nói về điều này trong Tập 260 của podcast Hanselminutes của anh ấy với Jonathan Carter. Họ giải thích rằng giao diện trôi chảy giống như giao diện người dùng trên API. Bạn không nên cung cấp giao diện thông thạo làm điểm truy cập duy nhất, mà nên cung cấp giao diện dưới dạng một giao diện người dùng mã ở trên "giao diện API thông thường".

Jonathan Carter cũng nói một chút về thiết kế API trên blog của mình .


Cảm ơn rất nhiều về các liên kết thông tin và giao diện người dùng trên API là một cách hay để xem xét nó.
Andrew Hanlon

14

Giao diện thông thạo là các tính năng rất mạnh để cung cấp trong ngữ cảnh mã của bạn, khi không có lý do "đúng".

Nếu mục đích của bạn chỉ đơn giản là tạo ra các chuỗi mã một dòng khổng lồ như một loại hộp đen giả, thì có lẽ bạn đang sủa sai cây. Mặt khác, nếu bạn đang sử dụng nó để tăng giá trị cho giao diện API của mình bằng cách cung cấp một phương tiện để thực hiện các cuộc gọi phương thức và cải thiện khả năng đọc mã, thì với rất nhiều kế hoạch và nỗ lực tốt, tôi nghĩ rằng nỗ lực đó là xứng đáng.

Tôi sẽ tránh làm theo những gì dường như trở thành một "mẫu" phổ biến khi tạo giao diện lưu loát, trong đó bạn đặt tên cho tất cả các phương thức lưu loát của mình là "-một cái gì đó, vì nó cướp đi giao diện API có khả năng tốt trong bối cảnh của nó và do đó giá trị nội tại của nó .

Điều quan trọng là nghĩ về cú pháp trôi chảy như một cách triển khai cụ thể của ngôn ngữ dành riêng cho Miền. Như một ví dụ thực sự tốt về những gì tôi đang nói, hãy xem StoryQ, sử dụng sự lưu loát như một phương tiện để thể hiện DSL một cách rất có giá trị và linh hoạt.


Cảm ơn bạn đã trả lời, không bao giờ là quá muộn để đưa ra một phản hồi được suy nghĩ kỹ.
Andrew Hanlon

Tôi không quan tâm đến tiền tố 'với' cho các phương thức. Nó đặt chúng ngoài các phương thức khác không trả về một đối tượng để xâu chuỗi. Ví dụ: position.withX(5)so vớiposition.getDistanceToOrigin()
Truyền thuyết Chiều dài

5

Lưu ý ban đầu: Tôi đang đưa ra một vấn đề với một giả định trong câu hỏi và rút ra kết luận cụ thể của tôi (ở cuối bài này) từ đó. Bởi vì điều này có lẽ không tạo ra một câu trả lời đầy đủ, bao gồm, tôi đánh dấu đây là CW.

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

Có thể dễ dàng được viết với các trình khởi tạo như:

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

Trong mắt tôi, hai phiên bản này phải có ý nghĩa và làm những điều khác nhau.

  • Không giống như phiên bản không thông thạo, phiên bản lưu loát che giấu thực tế rằng phiên bản mới Employeecũng Addphù hợp với bộ sưu tập Employees- nó chỉ gợi ý rằng một đối tượng mới là Created.

  • Ý nghĩa của ….WithX(…)nó là mơ hồ, đặc biệt đối với những người đến từ F #, có một withtừ khóa cho các biểu thức đối tượng : Họ có thể hiểu obj.WithX(x)là một đối tượng mới có nguồn gốc từ objđó giống hệt với objngoại trừ thuộc tính của nó X, có giá trị x. Mặt khác, với phiên bản thứ hai, rõ ràng không có đối tượng dẫn xuất nào được tạo và tất cả các thuộc tính được chỉ định cho đối tượng ban đầu.

….WithManager().With
  • Điều này ….With…còn có một ý nghĩa khác: chuyển "trọng tâm" của việc khởi tạo thuộc tính sang một đối tượng khác. Việc API thông thạo của bạn có hai ý nghĩa khác nhau Withđang gây khó khăn cho việc diễn giải chính xác những gì đang xảy ra, đó có lẽ là lý do tại sao bạn sử dụng thụt lề trong ví dụ của mình để thể hiện ý nghĩa dự định của mã đó. Nó sẽ rõ ràng hơn như thế này:

    (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 

Kết luận: "Ẩn" một tính năng ngôn ngữ đủ đơn giản new T { X = x }, với API thông thạo ( Ts.CreateNew().WithX(x)) rõ ràng có thể được thực hiện, nhưng:

  1. Phải cẩn thận rằng những người đọc mã thông thạo kết quả vẫn hiểu chính xác nó làm gì. Đó là, API lưu loát phải minh bạch về ý nghĩa và rõ ràng. Thiết kế một API như vậy có thể sẽ hiệu quả hơn dự kiến ​​(nó có thể phải được kiểm tra để dễ sử dụng và chấp nhận) và / hoặc

  2. thiết kế nó có thể tốn nhiều công sức hơn mức cần thiết: Trong ví dụ này, API thông thạo bổ sung rất ít "sự thoải mái cho người dùng" so với API cơ bản (một tính năng ngôn ngữ). Người ta có thể nói rằng một API thông thạo sẽ làm cho tính năng API / ngôn ngữ cơ bản trở nên "dễ sử dụng hơn"; đó là, nó sẽ tiết kiệm cho các lập trình viên một nỗ lực đáng kể. Nếu đó chỉ là một cách viết khác giống như vậy, thì có lẽ nó không đáng, bởi vì nó không làm cho cuộc sống của lập trình viên dễ dàng hơn, mà chỉ làm cho nhà thiết kế làm việc chăm chỉ hơn (xem kết luận # 1 ngay trên).

  3. Cả hai điểm trên đều âm thầm cho rằng API thông thạo là một lớp trên tính năng ngôn ngữ hoặc API hiện có. Giả định này có thể là một hướng dẫn tốt khác: API thông thạo có thể là một cách bổ sung để làm một cái gì đó, không phải là cách duy nhất. Đó là, có thể là một ý tưởng tốt để cung cấp API trôi chảy như một lựa chọn "chọn tham gia".


1
Cảm ơn bạn đã dành thời gian để thêm vào câu hỏi của tôi. Tôi sẽ thừa nhận rằng ví dụ mà tôi đã chọn là suy nghĩ kém. Vào thời điểm đó, tôi thực sự đang tìm cách sử dụng giao diện lỏng cho API truy vấn mà tôi đang phát triển. Tôi đơn giản hóa. Cảm ơn đã chỉ ra các lỗi, và cho các điểm kết luận tốt.
Andrew Hanlon

2

Tôi thích phong cách trôi chảy, nó thể hiện ý định rất rõ ràng. Với ví dụ initaliser đối tượng bạn có sau đó, bạn phải có setters tài sản công cộng để sử dụng cú pháp đó, bạn không với phong cách trôi chảy. Nói rằng, với ví dụ của bạn, bạn không đạt được nhiều hơn so với các setters công cộng vì bạn gần như đã sử dụng một kiểu phương thức java / esque set / get.

Điều đó đưa tôi đến điểm thứ hai, tôi không chắc liệu tôi có sử dụng phong cách trôi chảy theo cách bạn có không, với rất nhiều setters tài sản, có lẽ tôi sẽ sử dụng phiên bản thứ hai cho điều đó, tôi thấy nó tốt hơn khi bạn có rất nhiều động từ để xâu chuỗi lại với nhau, hoặc ít nhất là nhiều việc làm hơn là cài đặt.


Cảm ơn câu trả lời của bạn, tôi nghĩ rằng bạn đã thể hiện một quy tắc tốt: Thông thạo tốt hơn với nhiều cuộc gọi qua nhiều setters.
Andrew Hanlon

1

Tôi không quen thuộc với thuật ngữ giao diện trôi chảy , nhưng nó làm tôi nhớ đến một vài API tôi đã sử dụng bao gồm cả LINQ .

Cá nhân tôi không thấy các tính năng hiện đại của C # sẽ ngăn chặn sự hữu ích của cách tiếp cận như vậy. Tôi muốn nói rằng họ đi tay trong tay. Ví dụ, dễ dàng hơn để đạt được một giao diện như vậy bằng cách sử dụng các phương thức mở rộng .

Có lẽ làm rõ câu trả lời của bạn bằng một ví dụ cụ thể về cách thay thế giao diện trôi chảy bằng cách sử dụng một trong những tính năng hiện đại mà bạn đã đề cập.


1
Cảm ơn đã trả lời - Tôi đã thêm một ví dụ cơ bản để giúp làm rõ câu hỏi của tôi.
Andrew Hanlon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.