Phương pháp xích - tại sao nó là một thực hành tốt, hay không?


151

Phương thức xâu chuỗi là thực hành các phương thức đối tượng trả về chính đối tượng để kết quả được gọi cho phương thức khác. Như thế này:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

Đây dường như được coi là một thông lệ tốt, vì nó tạo ra mã có thể đọc được hoặc "giao diện trôi chảy". Tuy nhiên, đối với tôi, nó dường như phá vỡ ký hiệu gọi đối tượng được ngụ ý bởi chính hướng đối tượng - mã kết quả không biểu thị các hành động thực hiện cho kết quả của một phương thức trước đó, đó là cách mà mã hướng đối tượng thường hoạt động:

participant.getSchedule('monday').saveTo('monnday.file')

Sự khác biệt này quản lý để tạo ra hai ý nghĩa khác nhau cho ký hiệu dấu chấm "gọi đối tượng kết quả": Trong bối cảnh xâu chuỗi, ví dụ trên sẽ đọc là lưu đối tượng tham gia , mặc dù thực tế ví dụ này nhằm lưu lịch đối tượng nhận được bởi getSchedule.

Tôi hiểu rằng sự khác biệt ở đây là liệu phương thức được gọi có được mong đợi trả về một cái gì đó hay không (trong trường hợp đó nó sẽ trả về chính đối tượng được gọi để tạo chuỗi). Nhưng hai trường hợp này không thể phân biệt được với ký hiệu, chỉ từ ngữ nghĩa của các phương thức được gọi. Khi chuỗi phương thức không được sử dụng, tôi luôn có thể biết rằng một cuộc gọi phương thức hoạt động trên một cái gì đó liên quan đến kết quả của cuộc gọi trước đó - với chuỗi, giả định này bị phá vỡ và tôi phải xử lý toàn bộ chuỗi để hiểu đối tượng thực sự là gì gọi là thực sự là. Ví dụ:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

Có hai lệnh gọi phương thức cuối cùng đề cập đến kết quả của getSocialStream, trong khi các cuộc gọi trước đó đề cập đến người tham gia. Có thể thực tế là viết các chuỗi thực sự thay đổi bối cảnh (có phải vậy không?), Nhưng ngay cả khi đó bạn sẽ phải liên tục kiểm tra xem các chuỗi chấm trông giống nhau trên thực tế có được giữ trong cùng một bối cảnh hay chỉ hoạt động trên kết quả .

Đối với tôi, có vẻ như trong khi phương thức xâu chuỗi một cách hời hợt không tạo ra mã có thể đọc được, việc quá tải ý nghĩa của ký hiệu dấu chấm chỉ dẫn đến sự nhầm lẫn nhiều hơn. Vì tôi không coi mình là một chuyên gia lập trình, tôi cho rằng lỗi là của tôi. Vậy: tôi còn thiếu điều gì? Tôi có hiểu phương pháp xích nào đó sai không? Có một số trường hợp trong đó phương pháp xâu chuỗi đặc biệt tốt, hoặc một số trường hợp đặc biệt xấu?

Sidenote: Tôi hiểu câu hỏi này có thể được đọc như một tuyên bố về ý kiến ​​bị che dấu như một câu hỏi. Tuy nhiên, điều đó không phải - tôi thực sự muốn hiểu tại sao xâu chuỗi được coi là thực hành tốt và tôi đã sai ở đâu khi nghĩ rằng nó phá vỡ ký hiệu hướng đối tượng vốn có.


Có vẻ như phương thức xích là ít nhất một cách để viết mã thông minh trong java. Ngay cả khi không phải ai cũng đồng ý ..
Mercer Traieste

Họ cũng gọi những phương pháp "lưu loát" này hoặc giao diện "thông thạo". Bạn có thể muốn cập nhật tiêu đề của mình để sử dụng thuật ngữ này.
S.Lott

4
Trong một cuộc thảo luận SO khác, người ta nói rằng giao diện lưu loát là một khái niệm lớn hơn có liên quan đến khả năng đọc mã và chuỗi phương thức chỉ là một cách để hướng tới điều này. Tuy nhiên, chúng có liên quan chặt chẽ với nhau, vì vậy tôi đã thêm thẻ và các giao diện lưu loát trong văn bản - tôi nghĩ rằng những điều này là đủ.
Ilari Kajaste

14
Cách tôi nghĩ về điều này , trên thực tế, đó là một phương pháp tấn công để có được một tính năng bị thiếu trong cú pháp ngôn ngữ. Sẽ thực sự không cần thiết nếu có một ký hiệu thay thế tích hợp sẵn .sẽ bỏ qua mọi giá trị trả về mehtod và luôn gọi bất kỳ phương thức xích nào sử dụng cùng một đối tượng.
Ilari Kajaste

Nó là một thành ngữ mã hóa tuyệt vời nhưng giống như tất cả các công cụ tuyệt vời, nó bị lạm dụng.
Martin Spamer

Câu trả lời:


74

Tôi đồng ý rằng điều này là chủ quan. Đối với hầu hết các phần, tôi tránh xâu chuỗi phương thức, nhưng gần đây tôi cũng tìm thấy một trường hợp đúng, tôi đã có một phương thức chấp nhận một cái gì đó như 10 tham số, và cần nhiều hơn, nhưng trong hầu hết thời gian bạn chỉ phải xác định một vài. Với phần ghi đè này trở nên rất cồng kềnh rất nhanh. Thay vào đó tôi đã chọn cách tiếp cận chuỗi:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

Cách tiếp cận chuỗi phương thức là tùy chọn, nhưng nó giúp viết mã dễ dàng hơn (đặc biệt là với IntelliSense). Xin lưu ý rằng đây là một trường hợp riêng biệt và không phải là thông lệ chung trong mã của tôi.

Vấn đề là - trong 99% trường hợp bạn có thể làm tốt hoặc thậm chí tốt hơn mà không cần phương pháp xâu chuỗi. Nhưng có 1% trong đó đây là cách tiếp cận tốt nhất.


4
IMO, cách tốt nhất để sử dụng chuỗi phương thức trong kịch bản này là tạo một đối tượng tham số được truyền cho hàm, như thế nào P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Bạn có lợi thế là có thể sử dụng lại đối tượng tham số này trong các cuộc gọi khác, đó là một lợi thế!
pedromanoel

20
Chỉ cần phần trăm đóng góp của tôi về các mẫu, phương thức xuất xưởng thường chỉ có một điểm tạo và các sản phẩm là một lựa chọn tĩnh dựa trên các tham số của phương thức xuất xưởng. Việc tạo chuỗi trông giống với mẫu Builder hơn khi bạn gọi các phương thức khác nhau để thu được kết quả, các phương thức có thể là tùy chọn như trong chuỗi Phương thức , chúng ta có thể có một cái gì đó giống như PizzaBuilder.AddSauce().AddDough().AddTopping()nhiều tài liệu tham khảo hơn ở đây
Marco Medrano 15/03/13

3
Phương pháp xâu chuỗi (như chown trong câu hỏi ban đầu) được coi là xấu khi nó vi phạm luật của demeter . Xem: ifacethoughts.net/2006/03/07/ Từ Câu trả lời được đưa ra ở đây trên thực tế tuân theo luật đó vì đây là "mẫu xây dựng".
Thiên thần O'Sphere

2
@Marco Medrano Ví dụ về PizzaBuilder luôn làm tôi khó chịu kể từ khi tôi đọc nó trong JavaWorld từ lâu. Tôi cảm thấy tôi nên thêm nước sốt vào Pizza của tôi, không phải cho đầu bếp của tôi.
Breandán Dalton

1
Tôi hiểu ý của bạn, Vilx. Nhưng khi tôi đọc list.add(someItem), tôi đọc rằng "mã này đang thêm someItemvào listđối tượng". Vì vậy, khi tôi đọc PizzaBuilder.AddSauce(), tôi đọc điều này một cách tự nhiên là "mã này đang thêm nước sốt vào PizzaBuilderđối tượng". Nói cách khác, tôi thấy trình soạn thảo (người thực hiện thêm) là phương thức trong đó mã list.add(someItem)hoặc PizzaBuilder.addSauce()được nhúng. Nhưng tôi chỉ nghĩ ví dụ PizzaBuilder là một chút giả tạo. Ví dụ của bạn MyObject.SpecifySomeParameter(asdasd)làm việc tốt cho tôi.
Breandán Dalton

78

Chỉ cần 2 xu của tôi;

Phương pháp xâu chuỗi làm cho việc sửa lỗi trở nên khó khăn: - Bạn không thể đặt điểm dừng ở một điểm ngắn gọn để bạn có thể tạm dừng chương trình chính xác ở nơi bạn muốn - Nếu một trong các phương pháp này đưa ra một ngoại lệ và bạn nhận được số dòng, bạn không biết phương pháp nào trong "chuỗi" gây ra vấn đề.

Tôi nghĩ nói chung là thực hành tốt để luôn viết những dòng rất ngắn gọn và súc tích. Mỗi dòng chỉ nên thực hiện một cuộc gọi phương thức. Thích nhiều dòng hơn các dòng dài hơn.

EDIT: nhận xét đề cập rằng phương pháp xâu chuỗi và ngắt dòng là riêng biệt. Điều đó đúng. Tuy nhiên, tùy thuộc vào trình gỡ lỗi, có thể hoặc không thể đặt điểm dừng ở giữa câu lệnh. Ngay cả khi bạn có thể, sử dụng các dòng riêng biệt với các biến trung gian sẽ giúp bạn linh hoạt hơn rất nhiều và toàn bộ các giá trị bạn có thể kiểm tra trong cửa sổ Xem giúp quá trình gỡ lỗi.


4
Không sử dụng dòng mới và phương pháp xâu chuỗi độc lập? Bạn có thể xâu chuỗi với mỗi cuộc gọi trên một dòng mới theo câu trả lời @ Vilx- và bạn thường có thể đặt nhiều câu lệnh riêng biệt trên cùng một dòng (ví dụ: sử dụng dấu chấm phẩy trong Java).
brabster

2
Đây là câu trả lời hoàn toàn hợp lý, nhưng nó chỉ cho thấy một điểm yếu trong tất cả các trình gỡ lỗi mà tôi biết và không liên quan cụ thể đến câu hỏi.
masterxilo

1
@Brabster. Chúng có thể riêng biệt cho một số trình gỡ lỗi nhất định, tuy nhiên, có các cuộc gọi riêng với các biến trung gian vẫn mang lại cho bạn nhiều thông tin lớn hơn nhiều trong khi bạn điều tra các lỗi.
RAY

4
+1, tại thời điểm nào bạn không biết mỗi phương thức được trả về khi bước gỡ lỗi chuỗi phương thức. Mô hình chuỗi phương thức là một hack. Đó là cơn ác mộng tồi tệ nhất của lập trình viên bảo trì.
Lee Kowalkowski

1
Tôi cũng không phải là một fan hâm mộ lớn của chuỗi nhưng tại sao không chỉ đơn giản là đặt điểm dừng bên trong các định nghĩa phương thức?
Pankaj Sharma

39

Cá nhân, tôi thích các phương thức xích chỉ hành động trên đối tượng ban đầu, ví dụ: đặt nhiều thuộc tính hoặc gọi các phương thức kiểu tiện ích.

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

Tôi không sử dụng nó khi một hoặc nhiều phương thức xích sẽ trả về bất kỳ đối tượng nào ngoài foo trong ví dụ của tôi. Mặc dù về mặt cú pháp, bạn có thể xâu chuỗi bất cứ thứ gì miễn là bạn đang sử dụng API chính xác cho đối tượng đó trong chuỗi, việc thay đổi đối tượng IMHO làm cho mọi thứ trở nên dễ đọc hơn và có thể thực sự khó hiểu nếu API cho các đối tượng khác nhau có bất kỳ điểm tương đồng nào. Nếu bạn làm một số phương pháp gọi thực sự phổ biến vào cuối ( .toString(), .print(), bất cứ điều gì) mà đối tượng được bạn cuối cùng hành động dựa trên? Ai đó tình cờ đọc mã có thể không nắm bắt được rằng đó sẽ là một đối tượng được trả lại ngầm trong chuỗi chứ không phải là tham chiếu ban đầu.

Xâu chuỗi các đối tượng khác nhau cũng có thể dẫn đến lỗi null bất ngờ. Trong ví dụ của tôi, giả sử rằng foo là hợp lệ, tất cả các lệnh gọi phương thức là "an toàn" (ví dụ: hợp lệ cho foo). Trong ví dụ của OP:

participant.getSchedule('monday').saveTo('monnday.file')

... không có gì đảm bảo (vì một nhà phát triển bên ngoài đang xem mã) rằng getSchedule sẽ thực sự trả về một đối tượng lịch biểu không hợp lệ. Ngoài ra, việc gỡ lỗi kiểu mã này thường khó hơn rất nhiều vì nhiều IDE sẽ không đánh giá cuộc gọi phương thức tại thời điểm gỡ lỗi là một đối tượng mà bạn có thể kiểm tra. IMO, bất cứ lúc nào bạn có thể cần một đối tượng để kiểm tra cho mục đích gỡ lỗi, tôi thích có nó trong một biến rõ ràng.


Nếu có khả năng là Participantkhông có giá trị Schedulethì getSchedulephương thức được thiết kế để trả về một Maybe(of Schedule)loại và saveTophương thức được thiết kế để chấp nhận một Maybeloại.
Lightman

24

Martin Fowler có một cuộc thảo luận tốt ở đây:

Phương pháp Chaining

Khi nào sử dụng nó

Phương pháp Chaining có thể bổ sung rất nhiều vào khả năng đọc của DSL nội bộ và kết quả là nó gần như trở thành một synonum cho DSL nội bộ trong một số trường hợp. Phương pháp Chaining là tốt nhất, tuy nhiên, khi nó được sử dụng cùng với các kết hợp chức năng khác.

Phương pháp Chaining đặc biệt hiệu quả với các ngữ pháp như cha mẹ :: = (this | that) *. Việc sử dụng các phương thức khác nhau cung cấp cách dễ đọc để xem đối số nào sẽ đến tiếp theo. Các đối số tùy chọn tương tự có thể dễ dàng được bỏ qua với Phương thức Chuỗi. Một danh sách các mệnh đề bắt buộc, chẳng hạn như cha mẹ :: = giây đầu tiên không hoạt động tốt với dạng cơ bản, mặc dù nó có thể được hỗ trợ tốt bằng cách sử dụng các giao diện lũy tiến. Hầu hết thời gian tôi thích Chức năng lồng nhau cho trường hợp đó.

Vấn đề lớn nhất đối với Phương pháp Chuỗi là vấn đề hoàn thiện. Mặc dù có cách giải quyết, thông thường nếu bạn gặp phải vấn đề này, bạn nên sử dụng Hàm lồng nhau. Chức năng lồng nhau cũng là một lựa chọn tốt hơn nếu bạn đang gặp rắc rối với Biến bối cảnh.


2
Liên kết đã chết :(
Qw3ry

Bạn có ý nghĩa gì với DSL? Ngôn ngữ cụ thể của miền
Sören

@ Sören: Fowler đề cập đến các ngôn ngữ dành riêng cho tên miền.
Dirk Vollmar

21

Theo tôi, phương pháp xâu chuỗi là một chút mới lạ. Chắc chắn, nó trông rất tuyệt nhưng tôi không thấy bất kỳ lợi thế thực sự nào trong đó.

Thế nào là:

someList.addObject("str1").addObject("str2").addObject("str3")

bất kỳ tốt hơn:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

Ngoại lệ có thể là khi addObject () trả về một đối tượng mới, trong trường hợp đó, mã không xác định có thể phức tạp hơn một chút như:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

9
Nó ngắn gọn hơn, khi bạn tránh hai trong số các phần 'someList' ngay cả trong ví dụ đầu tiên của bạn và kết thúc bằng một dòng thay vì ba. Bây giờ nếu điều đó thực sự tốt hay xấu phụ thuộc vào những thứ khác nhau và có lẽ là vấn đề của hương vị.
Fabian Steeg

26
Ưu điểm thực sự của việc có 'someList' chỉ một lần, đó là việc đặt cho nó một cái tên mô tả dài hơn, dễ dàng hơn nhiều. Bất cứ khi nào một tên cần xuất hiện nhiều lần liên tiếp, sẽ có xu hướng rút ngắn (để giảm sự lặp lại và cải thiện khả năng đọc), làm cho nó ít mô tả, gây tổn thương cho khả năng đọc.
Chris Dodd

Điểm hay về khả năng đọc và các nguyên tắc DRY với sự cảnh báo rằng phương pháp xâu chuỗi ngăn chặn việc xem xét giá trị trả về của một phương thức đã cho và / hoặc ngụ ý một giả định về danh sách / bộ trống và giá trị không rỗng từ mọi phương thức trong chuỗi (gây ra sai lầm nhiều NPE trong các hệ thống tôi phải gỡ lỗi / sửa chữa thường xuyên).
Darrell Teague

@ChrisDodd Bạn chưa bao giờ nghe nói về việc hoàn thành tự động?
inf3rno

1
"Bộ não con người rất giỏi trong việc nhận ra sự lặp lại văn bản nếu nó có cùng một vết lõm" - đó chính xác là vấn đề với sự lặp lại: nó khiến não tập trung vào mô hình lặp lại. Vì vậy, để đọc VÀ HIỂU mã, bạn phải buộc bộ não của mình nhìn xa hơn / đằng sau mô hình lặp lại để xem điều gì đang thực sự xảy ra, đó là sự khác biệt giữa mô hình lặp lại mà não bạn có xu hướng che lấp. Đây là lý do tại sao sự lặp lại rất tệ cho khả năng đọc mã.
Chris Dodd

7

Điều này rất nguy hiểm vì bạn có thể phụ thuộc vào nhiều đối tượng hơn dự kiến, như sau đó cuộc gọi của bạn trả về một thể hiện của một lớp khác:

Tôi sẽ lấy một ví dụ:

foodStore là một đối tượng bao gồm nhiều cửa hàng thực phẩm bạn sở hữu. foodstore.getLocalStore () trả về một đối tượng chứa thông tin trên cửa hàng gần nhất với tham số. getpriceforSản phẩm (bất cứ điều gì) là một phương thức của đối tượng đó.

Vì vậy, khi bạn gọi foodStore.getLocalStore (tham số) .getpriceforSản phẩm (bất cứ điều gì)

bạn không chỉ phụ thuộc vào FoodStore như bạn mà còn phụ thuộc vào LocalStore.

Nếu getpriceforSản phẩm (bất cứ điều gì) thay đổi, bạn cần thay đổi không chỉ FoodStore mà cả lớp được gọi là phương thức xích.

Bạn nên luôn luôn nhắm đến việc ghép lỏng giữa các lớp.

Điều đó đang được nói, cá nhân tôi thích xâu chuỗi chúng khi lập trình Ruby.


7

Nhiều người sử dụng phương pháp xâu chuỗi như một hình thức thuận tiện hơn là có bất kỳ mối quan tâm về khả năng đọc nào trong tâm trí. Phương thức xâu chuỗi có thể chấp nhận được nếu nó liên quan đến việc thực hiện cùng một hành động trên cùng một đối tượng - nhưng chỉ khi nó thực sự tăng cường khả năng đọc và không chỉ để viết ít mã hơn.

Thật không may, nhiều người sử dụng phương pháp xâu chuỗi theo các ví dụ được đưa ra trong câu hỏi. Mặc dù chúng vẫn có thể được đọc, nhưng chúng không may gây ra sự ghép cao giữa nhiều lớp, vì vậy điều đó không được mong muốn.


6

Điều này có vẻ hơi chủ quan.

Phương pháp xâu chuỗi không phải là thứ gì đó vốn dĩ là xấu hay imo tốt.

Khả năng đọc là điều quan trọng nhất.

(Cũng xem xét rằng có một số lượng lớn các phương thức được xâu chuỗi sẽ khiến mọi thứ trở nên rất mong manh nếu có gì đó thay đổi)


Nó thực sự có thể là chủ quan, do đó thẻ chủ quan. Điều tôi hy vọng các câu trả lời sẽ làm nổi bật với tôi là trong trường hợp phương pháp xâu chuỗi sẽ là một ý tưởng hay - ngay bây giờ tôi không thấy gì nhiều, nhưng tôi nghĩ rằng đây chỉ là sự thất bại của tôi trong việc hiểu các điểm tốt của khái niệm, hơn là một cái gì đó vốn đã xấu trong chuỗi chính nó.
Ilari Kajaste

Nó sẽ không tệ nếu nó dẫn đến khớp nối cao? Phá vỡ chuỗi thành các tuyên bố cá nhân không hạ thấp khả năng đọc.
aberrant80

1
phụ thuộc vào cách bạn muốn giáo điều. Nếu nó dẫn đến một cái gì đó dễ đọc hơn thì điều này có thể thích hợp hơn trong nhiều tình huống. Vấn đề lớn mà tôi gặp phải với cách tiếp cận này là hầu hết các phương thức trên một đối tượng sẽ trả về các tham chiếu đến chính đối tượng đó, nhưng thường thì phương thức sẽ trả về một tham chiếu đến một đối tượng con mà bạn có thể xâu chuỗi nhiều phương thức hơn. Một khi bạn bắt đầu làm điều này thì sẽ rất khó để một lập trình viên khác giải quyết những gì đang diễn ra. Ngoài ra, bất kỳ thay đổi nào về chức năng của một phương thức sẽ là một nỗi đau để gỡ lỗi trong một câu lệnh ghép lớn.
John Nicholas

6

Lợi ích của Chaining
tức là, nơi tôi thích sử dụng nó

Một lợi ích của việc xâu chuỗi mà tôi không thấy được đề cập là khả năng sử dụng nó trong khi bắt đầu biến đổi hoặc khi chuyển một đối tượng mới sang một phương thức, không chắc đây có phải là thực tiễn xấu hay không.

Tôi biết đây là ví dụ giả định nhưng nói rằng bạn có các lớp sau

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

Và giả sử bạn không có quyền truy cập vào lớp cơ sở hoặc Nói các giá trị mặc định là động, dựa trên thời gian, v.v. Có, bạn có thể khởi tạo sau đó, sau đó thay đổi các giá trị nhưng điều đó có thể trở nên cồng kềnh, đặc biệt nếu bạn vừa vượt qua các giá trị cho một phương thức:

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

Nhưng điều này không dễ đọc hơn nhiều:

  PrintLocation(New HomeLocation().X(1337))

Hoặc, một thành viên trong lớp thì sao?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

đấu với

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

Đây là cách tôi đã sử dụng chuỗi, và thông thường các phương thức của tôi chỉ dành cho cấu hình, vì vậy chúng chỉ dài 2 dòng, sau đó đặt giá trị Return Me. Đối với chúng tôi, nó đã dọn sạch các dòng rất lớn rất khó đọc và hiểu mã thành một dòng đọc như một câu. cái gì đó như

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

Vs cái gì đó như

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

Detriment Of Chaining
tức là nơi tôi không thích sử dụng nó

Tôi không sử dụng chuỗi khi có rất nhiều tham số để truyền vào các thường trình, chủ yếu là do các dòng rất dài và như OP đã đề cập, nó có thể gây nhầm lẫn khi bạn gọi các thường trình đến các lớp khác để chuyển đến một trong các các phương pháp xích.

Cũng có mối lo ngại rằng một thói quen sẽ trả về dữ liệu không hợp lệ, cho đến nay tôi chỉ sử dụng chuỗi khi tôi trả lại cùng một thể hiện được gọi. Như đã chỉ ra nếu bạn xâu chuỗi giữa các lớp bạn làm cho việc gỡ lỗi trở nên khó khăn hơn (cái nào trả về null?) Và có thể tăng sự phụ thuộc khớp nối giữa các lớp.

Phần kết luận

Giống như mọi thứ trong cuộc sống và lập trình, Chaining không tốt, cũng không xấu nếu bạn có thể tránh được điều xấu thì xích có thể là một lợi ích tuyệt vời.

Tôi cố gắng tuân theo các quy tắc này.

  1. Cố gắng không xâu chuỗi giữa các lớp
  2. Tạo thói quen đặc biệt cho chuỗi
  3. Chỉ làm một việc trong thói quen xâu chuỗi
  4. Sử dụng nó khi nó cải thiện khả năng đọc
  5. Sử dụng nó khi nó làm cho mã đơn giản hơn

6

Phương thức xích có thể cho phép thiết kế DSL tiên tiến trong Java trực tiếp. Về bản chất, bạn có thể mô hình hóa ít nhất các loại quy tắc DSL này:

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

Các quy tắc này có thể được thực hiện bằng các giao diện này

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

Với các quy tắc đơn giản này, bạn có thể triển khai các DSL phức tạp như SQL trực tiếp trong Java, như được thực hiện bởi jOOQ , một thư viện mà tôi đã tạo. Xem một ví dụ SQL khá phức tạp được lấy từ blog của tôi ở đây:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

Một ví dụ điển hình khác là jRTF , một DSL nhỏ được thiết kế để xử lý các tài liệu RTF trực tiếp trong Java. Một ví dụ:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );

@ user877329: Có, nó có thể được sử dụng trong hầu hết mọi ngôn ngữ lập trình hướng đối tượng, biết một số thứ như giao diện và đa hình phụ
Lukas Eder

4

Phương pháp xâu chuỗi có thể đơn giản là một điều mới lạ đối với hầu hết các trường hợp nhưng tôi nghĩ nó có chỗ đứng. Một ví dụ có thể được tìm thấy trong việc sử dụng Bản ghi hoạt động của CodeIgniter :

$this->db->select('something')->from('table')->where('id', $id);

Điều đó có vẻ sạch sẽ hơn (và theo ý nghĩa của tôi, theo ý kiến ​​của tôi) hơn:

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

Nó thực sự là chủ quan; Mọi người đều có quan điểm riêng của họ.


Đây là một ví dụ về Giao diện thông thạo với Chuỗi phương thức, vì vậy UseCase hơi khác ở đây. Bạn không chỉ xâu chuỗi mà còn tạo ra một ngôn ngữ cụ thể cho tên miền nội bộ dễ đọc. Trên một sidenote, ActiveRecord của CI không phải là ActiveRecord.
Gordon

3

Tôi nghĩ rằng ngụy biện chính đang nghĩ rằng đây là một cách tiếp cận hướng đối tượng nói chung khi thực tế nó là một cách tiếp cận lập trình chức năng hơn bất cứ điều gì khác.

Những lý do chính tôi sử dụng nó là vì cả khả năng đọc và ngăn mã của tôi bị ngập bởi các biến.

Tôi không thực sự hiểu những gì người khác đang nói khi họ nói rằng nó làm hỏng khả năng đọc. Nó là một trong những hình thức lập trình ngắn gọn và gắn kết nhất mà tôi đã sử dụng.

Ngoài ra này:

convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("Destination.wav");

là cách tôi thường sử dụng nó. Sử dụng nó để xâu chuỗi số lượng tham số không phải là cách tôi thường sử dụng nó. Nếu tôi muốn đặt số x tham số trong một cuộc gọi phương thức, tôi sẽ sử dụng cú pháp params :

công khai void foo (đối tượng params [] mục)

Và truyền các đối tượng dựa trên loại hoặc chỉ sử dụng một mảng kiểu dữ liệu hoặc bộ sưu tập tùy thuộc vào trường hợp sử dụng của bạn.


1
+1 về "sai lầm chính yếu đang nghĩ rằng đây là một cách tiếp cận hướng đối tượng nói chung khi thực tế nó là một cách tiếp cận lập trình chức năng hơn bất cứ điều gì khác". Ca sử dụng nổi bật là dành cho các thao tác không có trạng thái trên các đối tượng (trong đó thay vì thay đổi trạng thái của nó, bạn trả về một đối tượng mới mà bạn tiếp tục hành động). Các câu hỏi của OP và các câu trả lời khác cho thấy các hành động có trạng thái thực sự có vẻ khó xử với chuỗi.
OmerB

Vâng, bạn đúng, đó là một thao tác không có trạng thái, ngoại trừ tôi thường không tạo ra một đối tượng mới mà thay vào đó sử dụng phép tiêm phụ thuộc để biến nó thành một dịch vụ khả dụng. Và có trường hợp sử dụng trạng thái không phải là những gì tôi nghĩ rằng chuỗi phương pháp được dự định cho. Ngoại lệ duy nhất tôi thấy là nếu bạn khởi tạo dịch vụ DI với một số cài đặt và có một số loại cơ quan giám sát để theo dõi trạng thái như một dịch vụ COM nào đó. Chỉ cần IMHO.
Shane Thorndike

2

Tôi đồng ý, tôi đã thay đổi cách thực hiện giao diện lưu loát trong thư viện của tôi.

Trước:

collection.orderBy("column").limit(10);

Sau:

collection = collection.orderBy("column").limit(10);

Trong phần thực hiện "trước", các hàm đã sửa đổi đối tượng và kết thúc bằng return this. Tôi đã thay đổi việc thực hiện để trả về một đối tượng mới cùng loại .

Lý do của tôi cho sự thay đổi này :

  1. Giá trị trả về không liên quan gì đến hàm, nó hoàn toàn ở đó để hỗ trợ phần chuỗi, Nó đáng lẽ phải là một hàm void theo OOP.

  2. Phương thức xâu chuỗi trong các thư viện hệ thống cũng thực hiện theo cách đó (như linq hoặc chuỗi):

    myText = myText.trim().toUpperCase();
    
  3. Đối tượng ban đầu vẫn còn nguyên, cho phép người dùng API quyết định phải làm gì với nó. Nó cho phép:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Việc thực hiện sao chép cũng có thể được sử dụng để xây dựng các đối tượng:

    painting = canvas.withBackground('white').withPenSize(10);
    

    Trường hợp setBackground(color)hàm thay đổi thể hiện và trả về không có gì (như được cho là của nó) .

  5. Hành vi của các chức năng có thể dự đoán được nhiều hơn (Xem điểm 1 & 2).

  6. Sử dụng một tên biến ngắn cũng có thể làm giảm sự lộn xộn mã, mà không buộc api trên mô hình.

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

Kết luận:
Theo tôi, một giao diện lưu loát sử dụng một return thistriển khai là sai.


1
Nhưng việc không trả lại một thể hiện mới cho mỗi cuộc gọi sẽ tạo ra một chút chi phí, đặc biệt nếu bạn đang sử dụng các mục lớn hơn? Ít nhất là đối với các ngôn ngữ không được quản lý.
Apeiron

@Apeiron Hiệu suất-khôn ngoan chắc chắn sẽ nhanh hơn để return thisquản lý hoặc nếu không. Theo ý kiến ​​của tôi, nó có được một api trôi chảy theo cách "không tự nhiên". (Đã thêm lý do 6: để hiển thị một giải pháp thay thế không trôi chảy, không có chức năng bổ sung / bổ sung)
Bob Fanger

Tôi đồng ý với bạn. Thay vào đó, tốt hơn hết là để trạng thái cơ bản của DSL không bị ảnh hưởng và trả về các đối tượng mới trong mỗi cuộc gọi phương thức, thay vào đó ... Tôi tò mò: Thư viện mà bạn đã đề cập là gì?
Lukas Eder

1

Điểm hoàn toàn bị bỏ lỡ ở đây, là phương thức xích cho phép DRY . Đó là một từ thay thế hiệu quả cho "với" (được triển khai kém trong một số ngôn ngữ).

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

Điều này quan trọng cho cùng một lý do DRY luôn luôn quan trọng; nếu A hóa ra là một lỗi và các thao tác này cần được thực hiện trên B, bạn chỉ cần cập nhật ở 1 nơi chứ không phải 3.

Thực tế, lợi thế là nhỏ trong trường hợp này. Tuy nhiên, ít gõ hơn một chút, mạnh mẽ hơn (DRY), tôi sẽ lấy nó.


14
Lặp lại một tên biến trong mã nguồn không liên quan gì đến nguyên tắc DRY. DRY tuyên bố rằng "Mỗi phần kiến ​​thức phải có một đại diện duy nhất, rõ ràng, có thẩm quyền trong một hệ thống", hoặc nói cách khác, tránh sự lặp lại của kiến thức (không phải sự lặp lại của văn bản ).
pedromanoel

4
Nó chắc chắn là sự lặp lại mà vi phạm khô. Lặp đi lặp lại tên biến (không cần thiết) gây ra tất cả các tội ác theo cùng một cách mà các hình thức khô khan khác làm: Nó tạo ra nhiều phụ thuộc hơn và nhiều công việc hơn. Trong ví dụ trên, nếu chúng ta đổi tên A, phiên bản ướt sẽ cần 3 thay đổi và sẽ gây ra lỗi để gỡ lỗi nếu bất kỳ một trong ba lỗi bị bỏ sót.
Anfurny

1
Tôi không thể thấy vấn đề bạn đã chỉ ra trong ví dụ này, vì tất cả các lệnh gọi phương thức đều gần nhau. Ngoài ra, nếu bạn quên thay đổi tên của một biến trong một dòng, trình biên dịch sẽ trả về lỗi và điều này sẽ được sửa trước khi bạn có thể chạy chương trình của mình. Ngoài ra, tên của một biến được giới hạn trong phạm vi khai báo của nó. Trừ khi biến này là toàn cầu, vốn đã là một thực tiễn lập trình xấu. IMO, DRY không phải là về việc gõ ít hơn, mà là giữ cho mọi thứ bị cô lập.
pedromanoel

"Trình biên dịch" có thể trả về lỗi hoặc có thể bạn đang sử dụng PHP hoặc JS và ở đó trình thông dịch chỉ có thể phát ra lỗi khi chạy khi bạn gặp tình trạng này.
Anfurny

1

Tôi thường ghét phương pháp xâu chuỗi vì tôi nghĩ nó làm xấu đi khả năng đọc. Sự nhỏ gọn thường bị nhầm lẫn với khả năng đọc, nhưng chúng không giống nhau. Nếu bạn làm mọi thứ trong một câu lệnh thì đó là nhỏ gọn, nhưng hầu như ít đọc hơn (khó theo dõi hơn) so với thực hiện trong nhiều câu lệnh. Như bạn đã nhận thấy trừ khi bạn không thể đảm bảo rằng giá trị trả về của các phương thức được sử dụng là như nhau, thì chuỗi phương thức sẽ là một nguồn gây nhầm lẫn.

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

đấu với

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

đấu với

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

đấu với

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

Như bạn có thể thấy bạn thắng gần như không có gì, bởi vì bạn phải thêm ngắt dòng vào câu lệnh duy nhất của mình để dễ đọc hơn và bạn phải thêm thụt lề để làm rõ rằng bạn đang nói về các đối tượng khác nhau. Chà, nếu tôi muốn sử dụng ngôn ngữ dựa trên nhận dạng, thì tôi sẽ học Python thay vì làm điều này, chưa kể rằng hầu hết các IDE sẽ xóa thụt lề bằng cách tự động định dạng mã.

Tôi nghĩ rằng nơi duy nhất mà loại chuỗi này có thể hữu ích là các luồng đường ống trong CLI hoặc THAM GIA nhiều truy vấn cùng nhau trong SQL. Cả hai đều có giá cho nhiều báo cáo. Nhưng nếu bạn muốn giải quyết các vấn đề phức tạp, bạn sẽ kết thúc ngay cả với những người trả giá và viết mã trong nhiều câu lệnh bằng cách sử dụng các biến hoặc viết tập lệnh bash và các thủ tục hoặc khung nhìn được lưu trữ.

Theo cách giải thích của DRY: "Tránh sự lặp lại của kiến ​​thức (không phải sự lặp lại của văn bản)." và "Gõ ít hơn, thậm chí không lặp lại văn bản.", cái đầu tiên mà nguyên tắc thực sự có nghĩa là gì, nhưng cái thứ hai là sự hiểu lầm phổ biến vì nhiều người không thể hiểu được sự nhảm nhí quá phức tạp như "Mỗi phần kiến ​​thức phải có một, không rõ ràng, đại diện có thẩm quyền trong một hệ thống ". Cái thứ hai là sự nhỏ gọn bằng mọi giá, phá vỡ trong kịch bản này, vì nó làm xấu đi khả năng đọc. Việc giải thích đầu tiên bị phá vỡ bởi DDD khi bạn sao chép mã giữa các bối cảnh bị ràng buộc, bởi vì khớp nối lỏng lẻo là quan trọng hơn trong kịch bản đó.


0

Tốt:

  1. Thật ngắn gọn, nhưng cho phép bạn đặt nhiều hơn vào một dòng một cách thanh lịch.
  2. Đôi khi bạn có thể tránh việc sử dụng một biến, đôi khi có thể hữu ích.
  3. Nó có thể thực hiện tốt hơn.

Những người xấu:

  1. Bạn đang thực hiện trả về, về cơ bản là thêm chức năng cho các phương thức trên các đối tượng không thực sự là một phần của những phương thức đó nhằm làm gì. Nó trả lại một cái gì đó bạn đã hoàn toàn có để tiết kiệm một vài byte.
  2. Nó ẩn các chuyển mạch ngữ cảnh khi một chuỗi dẫn đến một chuỗi khác. Bạn có thể có được điều này với getters, ngoại trừ nó khá rõ ràng khi bối cảnh chuyển đổi.
  3. Xâu chuỗi trên nhiều dòng trông xấu, không chơi tốt với thụt lề và có thể gây ra một số thao tác xử lý nhầm lẫn (đặc biệt là trong các ngôn ngữ với ASI).
  4. Nếu bạn muốn bắt đầu trả lại một cái gì đó hữu ích cho phương thức xích, bạn có thể sẽ gặp khó khăn hơn trong việc sửa nó hoặc gặp nhiều vấn đề hơn với nó.
  5. Bạn đang giảm tải quyền kiểm soát cho một thực thể mà bạn thường không giảm tải để hoàn toàn thuận tiện, ngay cả trong các ngôn ngữ được gõ chính xác gây ra bởi điều này không thể luôn luôn được phát hiện.
  6. Nó có thể thực hiện tồi tệ hơn.

Chung:

Một cách tiếp cận tốt là không sử dụng chuỗi nói chung cho đến khi tình huống phát sinh hoặc các mô-đun cụ thể sẽ đặc biệt phù hợp với nó.

Xâu chuỗi có thể làm tổn thương khả năng đọc khá nghiêm trọng trong một số trường hợp, đặc biệt là khi cân ở điểm 1 và 2.

Về việc tích lũy, nó có thể bị sử dụng sai, chẳng hạn như thay vì một cách tiếp cận khác (ví dụ truyền một mảng) hoặc trộn các phương thức theo những cách kỳ quái (Parent.setS Something (). GetChild (). SetS Something (). GetParent (). SetS Something ()).


0

Ý kiến ​​trả lời

Hạn chế lớn nhất của chuỗi là người đọc khó có thể hiểu được mỗi phương thức ảnh hưởng đến đối tượng ban đầu như thế nào, nếu có, và loại phương thức nào trả về.

Vài câu hỏi:

  • Các phương thức trong chuỗi trả về một đối tượng mới, hoặc cùng một đối tượng bị đột biến?
  • Có phải tất cả các phương thức trong chuỗi trả về cùng một loại?
  • Nếu không, làm thế nào được chỉ định khi một loại trong chuỗi thay đổi?
  • Giá trị được trả về bằng phương pháp cuối cùng có thể được loại bỏ một cách an toàn không?

Gỡ lỗi, trong hầu hết các ngôn ngữ, thực sự có thể khó hơn với chuỗi. Ngay cả khi mỗi bước trong chuỗi nằm trên một dòng riêng (loại nào đánh bại mục đích của chuỗi), khó có thể kiểm tra giá trị được trả về sau mỗi bước, đặc biệt đối với các phương pháp không đột biến.

Thời gian biên dịch có thể chậm hơn tùy thuộc vào ngôn ngữ và trình biên dịch, vì các biểu thức có thể phức tạp hơn nhiều để giải quyết.

Tôi tin rằng giống như với mọi thứ, xích là một giải pháp tốt có thể có ích trong một số tình huống. Nó nên được sử dụng một cách thận trọng, hiểu ý nghĩa và giới hạn số lượng các yếu tố chuỗi với một số ít.


0

Trong các ngôn ngữ được nhập (thiếu autohoặc tương đương), điều này giúp người thực hiện không phải khai báo các loại kết quả trung gian.

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

Đối với các chuỗi dài hơn, bạn có thể xử lý một số loại trung gian khác nhau, bạn cần khai báo từng loại.

Tôi tin rằng cách tiếp cận này thực sự được phát triển trong Java trong đó a) tất cả các lệnh gọi hàm là các yêu cầu hàm thành viên và b) các kiểu rõ ràng là bắt buộc. Tất nhiên có một sự đánh đổi ở đây về mặt, mất một số nhân chứng, nhưng trong một số tình huống, một số người thấy nó có giá trị trong khi.

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.