Có phải đó là một thực tế tồi để thực hiện một setter quay trở lại với điều này không?


249

Nó là một ý tưởng tốt hay xấu để làm cho setters trong java trả lại "cái này"?

public Employee setName(String name){
   this.name = name;
   return this;
}

Mẫu này có thể hữu ích vì sau đó bạn có thể xâu chuỗi setters như thế này:

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

thay vì điều này:

Employee e = new Employee();
e.setName("Jack Sparrow");
...and so on...
list.add(e);

... nhưng nó đi ngược lại quy ước tiêu chuẩn. Tôi cho rằng nó có thể đáng giá chỉ vì nó có thể khiến setter đó làm một cái gì đó hữu ích khác. Tôi đã thấy mẫu này được sử dụng ở một số nơi (ví dụ: JMock, JPA), nhưng có vẻ không phổ biến và thường chỉ được sử dụng cho các API được xác định rất rõ trong đó mẫu này được sử dụng ở mọi nơi.

Cập nhật:

Những gì tôi đã mô tả rõ ràng là hợp lệ, nhưng những gì tôi thực sự tìm kiếm là một số suy nghĩ về việc liệu điều này thường được chấp nhận, và nếu có bất kỳ cạm bẫy hoặc thực tiễn tốt nhất liên quan. Tôi biết về mẫu Builder nhưng nó liên quan nhiều hơn một chút sau đó là những gì tôi đang mô tả - như Josh Bloch mô tả nó có một lớp Builder tĩnh liên quan để tạo đối tượng.


1
Vì tôi đã thấy mẫu thiết kế này một thời gian trước đây, tôi làm điều này bất cứ nơi nào có thể. Nếu một phương thức không rõ ràng cần phải trả lại một cái gì đó để thực hiện công việc của nó, thì bây giờ nó sẽ trả về this. Đôi khi, tôi thậm chí còn thay đổi hàm để thay vì trả về một giá trị, nó hoạt động trên một thành viên của đối tượng, để tôi có thể làm điều này. Rất tuyệt vời. :)
Inversus

4
Đối với setters kính thiên văn tự trả về và trong các trình xây dựng, tôi thích sử dụng withName (Tên chuỗi) thay vì setName (Tên chuỗi). Như bạn đã chỉ ra một thực tiễn phổ biến và kỳ vọng cho setter là trả về void. Các setters "không chuẩn" có thể không hoạt động tốt với các khung hiện có, ví dụ như các trình quản lý thực thể JPA, Spring, v.v.
o

Vui lòng giới thiệu ngắt dòng trước mỗi lần gọi :) Và định cấu hình IDE của bạn hoặc nhận một dòng thích hợp nếu nó không tôn trọng điều này.
MauganRa

Các khung được sử dụng rộng rãi (chẳng hạn như Spring và Hibernate) sẽ tuân thủ nghiêm ngặt (ít nhất là chúng đã từng) tuân thủ quy ước void-setters
Legna

Câu trả lời:


83

Tôi không nghĩ có gì đặc biệt sai với nó, đó chỉ là vấn đề về phong cách. Nó hữu ích khi:

  • Bạn cần đặt nhiều trường cùng một lúc (bao gồm cả lúc xây dựng)
  • bạn biết những lĩnh vực nào bạn cần đặt tại thời điểm bạn viết mã và
  • có nhiều kết hợp khác nhau cho các trường bạn muốn đặt.

Các lựa chọn thay thế cho phương pháp này có thể là:

  1. Một hàm tạo lớn (nhược điểm: bạn có thể chuyển nhiều giá trị null hoặc giá trị mặc định và thật khó để biết giá trị nào tương ứng với giá trị nào)
  2. Một số hàm tạo quá tải (nhược điểm: trở nên khó sử dụng khi bạn có nhiều hơn một vài)
  3. Các phương thức nhà máy / tĩnh (nhược điểm: giống như các nhà xây dựng bị quá tải - trở nên khó sử dụng một khi có nhiều hơn một vài)

Nếu bạn chỉ thiết lập một vài thuộc tính tại một thời điểm tôi sẽ nói rằng không đáng để trả lại 'cái này'. Nó chắc chắn rơi xuống nếu sau đó bạn quyết định trả lại một cái gì đó khác, như một thông báo / chỉ báo trạng thái / thành công.


1
tốt, nói chung, bạn không trả lại bất cứ thứ gì từ một setter nào, theo quy ước.
Ken Liu

17
Có thể không bắt đầu với, nhưng một setter không nhất thiết phải giữ mục đích ban đầu của nó. Cái từng là biến có thể thay đổi thành trạng thái bao gồm một số biến hoặc có tác dụng phụ khác. Một số setters có thể trả về một giá trị trước đó, một số khác có thể trả về một chỉ báo lỗi nếu thất bại quá phổ biến đối với một ngoại lệ. Điều đó đặt ra một điểm thú vị khác: nếu một công cụ / khung mà bạn đang sử dụng không nhận ra setters của bạn khi chúng có giá trị trả về thì sao?
Tom Clift

12
@Tom điểm tốt, làm điều này phá vỡ quy ước "Java bean" cho getters và setters.
Andy White

2
@TomClift Việc phá vỡ quy ước "Java Bean" có gây ra vấn đề gì không? Là các thư viện sử dụng quy ước "Java Bean" nhìn vào kiểu trả về hoặc chỉ các tham số phương thức và tên phương thức.
Theo Briscoe

3
đó là lý do tại sao mẫu Builder tồn tại, setters không nên trả lại một cái gì đó, thay vào đó, hãy tạo một trình xây dựng nếu cần thiết có vẻ tốt hơn và mất ít mã hơn :)
RicardoE

106

Đó không phải là thực hành xấu. Đó là một thực tế ngày càng phổ biến. Hầu hết các ngôn ngữ không yêu cầu bạn phải xử lý đối tượng được trả về nếu bạn không muốn nó không thay đổi cú pháp sử dụng setter "bình thường" nhưng cho phép bạn xâu chuỗi setters lại với nhau.

Điều này thường được gọi là mô hình xây dựng hoặc giao diện trôi chảy .

Nó cũng phổ biến trong API Java:

String s = new StringBuilder().append("testing ").append(1)
  .append(" 2 ").append(3).toString();

26
Nó thường được sử dụng trong các trình xây dựng, nhưng tôi sẽ không nói "đây được gọi là ... mẫu xây dựng".
Laurence Gonsalves

10
Thật buồn cười với tôi rằng một số lý do cho các giao diện trôi chảy là chúng làm cho mã dễ đọc hơn. Tôi có thể thấy nó thuận tiện hơn để viết, nhưng nó khiến tôi khó đọc hơn. Đó là sự bất đồng thực sự duy nhất tôi có với nó.
Mã Brent viết

30
Nó còn được gọi là antipotype tàu hỏa. Vấn đề là khi một dấu vết ngăn xếp ngoại lệ con trỏ null chứa một dòng như thế này, bạn không biết lệnh gọi nào trả về null. Điều đó không có nghĩa là nên tránh xâu chuỗi bằng mọi giá, nhưng hãy cẩn thận với những thư viện tồi (đặc biệt là nhà pha chế).
ddimitrov

18
@ddimitrov càng lâu khi bạn giới hạn nó để trả lại thì điều này sẽ không bao giờ là vấn đề (chỉ có điều khoản đầu tiên có thể ném NPE)
Stefan

4
dễ dàng hơn để viết VÀ để đọc giả định rằng bạn đặt ngắt dòng và chú ý nơi khả năng đọc sẽ bị ảnh hưởng! (vì nó tránh được sự lộn xộn không cần thiết lặp lại mã như bla.foo.setBar1(...) ; bla.foo.setBar2(...)khi bạn có thể viết bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...)(không thể sử dụng linebreaks trong một chú thích SO như thế này :-( ... hy vọng bạn sẽ có được điểm xem xét 10 setters như vậy hoặc cuộc gọi phức tạp hơn)
Andreas Dietrich

90

Để tóm tắt:

  • nó được gọi là "giao diện trôi chảy" hay "chuỗi phương thức".
  • đây không phải là Java "tiêu chuẩn", mặc dù ngày nay bạn thấy nó nhiều hơn (hoạt động rất tốt trong jQuery)
  • nó vi phạm thông số JavaBean, do đó, nó sẽ phá vỡ các công cụ và thư viện khác nhau, đặc biệt là các trình xây dựng JSP và Spring.
  • nó có thể ngăn chặn một số tối ưu hóa mà JVM thường làm
  • Một số người nghĩ rằng nó làm sạch mã, những người khác nghĩ rằng nó "khủng khiếp"

Một vài điểm khác không được đề cập:

  • Điều này vi phạm hiệu trưởng rằng mỗi chức năng nên làm một (và chỉ một) điều. Bạn có thể hoặc không thể tin vào điều này, nhưng trong Java tôi tin rằng nó hoạt động tốt.

  • IDE sẽ không tạo ra chúng cho bạn (theo mặc định).

  • Cuối cùng tôi, đây là một điểm dữ liệu trong thế giới thực. Tôi đã có vấn đề khi sử dụng một thư viện được xây dựng như thế này. Trình tạo truy vấn của Hibernate là một ví dụ về điều này trong một thư viện hiện có. Vì các phương thức set * của Query đang trả về các truy vấn, nên không thể biết chỉ bằng cách xem chữ ký cách sử dụng nó. Ví dụ:

    Query setWhatever(String what);
  • Nó giới thiệu một sự mơ hồ: phương thức có sửa đổi đối tượng hiện tại (mẫu của bạn) hay, có lẽ Truy vấn thực sự bất biến (một mẫu rất phổ biến và có giá trị) và phương thức đang trả về một đối tượng mới. Nó chỉ làm cho thư viện khó sử dụng hơn và nhiều lập trình viên không khai thác tính năng này. Nếu setters là setters, nó sẽ rõ ràng hơn làm thế nào để sử dụng nó.


5
btw, đó là "trôi chảy", không phải "chất lỏng" ... vì trong đó cho phép bạn cấu trúc một loạt các cuộc gọi phương thức như một ngôn ngữ nói.
Ken Liu

1
Điểm cuối cùng về sự bất biến là rất quan trọng. Ví dụ đơn giản nhất là String. Các nhà phát triển Java hy vọng rằng khi sử dụng các phương thức trên String, họ nhận được cá thể hoàn toàn mới và không phải là cá thể được sửa đổi. Với giao diện lưu loát, nó sẽ phải được đề cập trong tài liệu phương thức rằng đối tượng được trả về là 'this' thay vì thể hiện mới.
MeTTeO

7
Mặc dù tôi đồng ý chung, tôi không đồng ý rằng điều này vi phạm hiệu trưởng "chỉ làm một việc". Trở về thislà khoa học tên lửa khó phức tạp. :-)
user949300

điểm bổ sung: nó cũng vi phạm nguyên tắc phân tách truy vấn Command.
Marton_hun

84

Tôi thích sử dụng các phương thức 'với' cho việc này:

public String getFoo() { return foo; }
public void setFoo(String foo) { this.foo = foo; }
public Employee withFoo(String foo) {
  setFoo(foo);
  return this;
}

Như vậy:

list.add(new Employee().withName("Jack Sparrow")
                       .withId(1)
                       .withFoo("bacon!"));

Cảnh báo : withXcú pháp này thường được sử dụng để cung cấp "setters" cho các đối tượng không thay đổi, vì vậy người gọi các phương thức này có thể mong đợi chúng tạo ra các đối tượng mới thay vì làm thay đổi thể hiện hiện tại. Có lẽ một từ ngữ hợp lý hơn sẽ là một cái gì đó như:

list.add(new Employee().chainsetName("Jack Sparrow")
                       .chainsetId(1)
                       .chainsetFoo("bacon!"));

Với quy ước đặt tên chainsetXyz () hầu như mọi người đều nên vui mừng.


18
+1 Cho quy ước thú vị. Tôi sẽ không áp dụng nó trong mã của riêng mình vì dường như bây giờ bạn phải có một get, a setvà a withcho mọi trường lớp. Đó vẫn là một giải pháp thú vị. :)
Paul Manta

1
Nó phụ thuộc vào tần suất bạn gọi các setters. Tôi đã thấy rằng nếu những setters đó được gọi rất nhiều, thì đáng để thêm rắc rối vì chúng đơn giản hóa mã ở mọi nơi khác. YMMV
Qualidafial

2
Và nếu bạn bổ sung này để Project Lombok's @Getter/@Setterchú thích ... mà muốn được tuyệt vời cho chaining. Hoặc bạn có thể sử dụng một cái gì đó rất giống như bộ Kestrelkết hợp ( github.com/raganwald/Katy ) mà các quái vật JQuery và Javascript sử dụng.
Ehtesh Choudhury

4
@ AlikElzin-kilaka Thật sự tôi chỉ nhận thấy rằng java.time lớp bất biến trong Java 8 sử dụng mô hình này, ví dụ như LocalDate.withMonth, withYear vv
qualidafial

4
các withtiền tố là một quy ước khác nhau. như @qualidafial đã đưa ra một ví dụ. các phương thức có tiền tố withkhông nên trả về thismà là một thể hiện mới như thể hiện tại nhưng withthay đổi đó. Điều này được thực hiện khi bạn muốn các đối tượng của mình là bất biến. Vì vậy, khi tôi thấy một phương thức có tiền tố với withtôi giả sử tôi sẽ nhận được một đối tượng mới, không phải cùng một đối tượng.
tempcke

26

Nếu bạn không muốn quay lại 'this'từ setter nhưng không muốn sử dụng tùy chọn thứ hai, bạn có thể sử dụng cú pháp sau để đặt thuộc tính:

list.add(new Employee()
{{
    setName("Jack Sparrow");
    setId(1);
    setFoo("bacon!");
}});

Như một bên tôi nghĩ rằng nó sạch hơn một chút trong C #:

list.Add(new Employee() {
    Name = "Jack Sparrow",
    Id = 1,
    Foo = "bacon!"
});

16
khởi tạo cú đúp có thể có vấn đề với bằng vì nó tạo ra một lớp bên trong ẩn danh; xem c2.com/cgi/wiki?DoubleBraceInitialization
Csaba_H

@Csaba_H Rõ ràng vấn đề đó là lỗi của người đã làm hỏng equalsphương pháp. Có nhiều cách rất rõ ràng để đối phó với các lớp ẩn danh equalsnếu bạn biết bạn đang làm gì.
AJMansfield

tạo một lớp mới (ẩn danh) chỉ cho điều đó? cho mọi trường hợp?
dùng85421

11

Nó không chỉ phá vỡ quy ước của getters / setters, nó còn phá vỡ khung tham chiếu phương thức Java 8. MyClass::setMyValuelà một BiConsumer<MyClass,MyValue>, và myInstance::setMyValuelà mộtConsumer<MyValue> . Nếu bạn có trả về setter của mình this, thì nó không còn là một thể hiện hợp lệ của Consumer<MyValue>, mà là một Function<MyValue,MyClass>, và sẽ khiến mọi thứ sử dụng tham chiếu phương thức đến các setters đó (giả sử chúng là các phương thức void) bị phá vỡ.


2
Thật tuyệt vời nếu Java có một số cách để quá tải theo kiểu trả về, không chỉ là JVM. Bạn có thể bỏ qua nhiều thay đổi vi phạm một cách dễ dàng.
Adowrath

1
Bạn luôn có thể xác định giao diện chức năng mở rộng cả hai Consumer<A>Function<A,B>bằng cách cung cấp cài đặt mặc định void accept(A a) { apply(a); }. Sau đó, nó có thể dễ dàng được sử dụng như một trong hai và sẽ không phá vỡ bất kỳ mã nào yêu cầu một hình thức cụ thể.
Steve K

Argh! Đây chỉ là một lỗi bình thường! Điều này được gọi là tương thích void. Một setter trả về một giá trị có thể đóng vai trò là Người tiêu dùng. ideone.com/ZIDy2M
Michael

8

Tôi không biết Java nhưng tôi đã làm điều này trong C ++. Những người khác đã nói rằng nó làm cho các dòng thực sự dài và rất khó đọc, nhưng tôi đã thực hiện nó như thế này rất nhiều lần:

list.add(new Employee()
    .setName("Jack Sparrow")
    .setId(1)
    .setFoo("bacon!"));

Điều này thậm chí còn tốt hơn:

list.add(
    new Employee("Jack Sparrow")
    .Id(1)
    .foo("bacon!"));

ít nhất, tôi nghĩ Nhưng bạn được chào đón để đánh giá thấp tôi và gọi cho tôi một lập trình viên khủng khiếp nếu bạn muốn. Và tôi không biết liệu bạn có được phép làm điều này trong Java không.


"Thậm chí tốt hơn" không cho vay tốt chức năng mã nguồn Format có sẵn trong IDE hiện đại. Không may.
Thorbjørn Ravn Andersen

bạn có thể đúng ... Công cụ định dạng tự động duy nhất tôi đã sử dụng là tự động thụt lề của emacs.
Carson Myers

2
Các trình định dạng mã nguồn có thể được ép buộc bằng một // đơn giản sau mỗi lần gọi phương thức trong chuỗi. Nó làm xấu mã của bạn lên một chút, nhưng không nhiều bằng các chuỗi câu lệnh dọc của bạn được định dạng lại theo chiều ngang.
Qualidafial

@qualidafial Bạn không cần //sau mỗi phương thức nếu bạn định cấu hình IDE của mình không tham gia các dòng đã được gói (ví dụ: Eclipse> Thuộc tính> Java> Kiểu mã> Trình định dạng> Gói dòng> Không bao giờ nối các dòng đã được bọc).
DJDaveMark

6

Lược đồ này (ý định chơi chữ), được gọi là 'giao diện lưu loát', hiện đang trở nên khá phổ biến. Nó chấp nhận được, nhưng nó không thực sự là tách trà của tôi.


6

Bởi vì nó không trả về void, nó không còn là trình thiết lập thuộc tính JavaBean hợp lệ. Điều đó có thể quan trọng nếu bạn là một trong bảy người trên thế giới sử dụng các công cụ "Bean Builder" trực quan hoặc một trong 17 người sử dụng các phần tử JSP-bean-setProperty.


Nó cũng quan trọng nếu bạn sử dụng các khung nhận biết đậu như Spring.
ddimitrov

6

Ít nhất là về lý thuyết , nó có thể làm hỏng các cơ chế tối ưu hóa của JVM bằng cách đặt các phụ thuộc sai giữa các cuộc gọi.

Nó được cho là đường cú pháp, nhưng trên thực tế có thể tạo ra các tác dụng phụ trong máy ảo Java 43 siêu thông minh.

Đó là lý do tại sao tôi bỏ phiếu không, không sử dụng nó.


10
Thú vị ... bạn có thể mở rộng về điều này một chút không?
Ken Liu

3
Chỉ cần nghĩ về cách bộ xử lý superscalar xử lý thực thi song song. Đối tượng để thực hiện setphương thức thứ hai trên phụ thuộc vào setphương thức đầu tiên mặc dù nó được lập trình viên biết.
Mary

2
Tôi vẫn không làm theo. Nếu bạn đặt Foo và sau đó là Bar với hai câu lệnh riêng biệt, đối tượng mà bạn đang đặt Thanh có trạng thái khác với đối tượng mà bạn đang đặt Foo. Vì vậy, trình biên dịch cũng không thể song song hóa các câu lệnh đó. Ít nhất, tôi không thấy nó có thể như thế nào mà không đưa ra một giả định không chính đáng. (Vì tôi không biết gì về nó, nên thực tế tôi không phủ nhận rằng Java 43 thực hiện song song trong trường hợp một lần nhưng không phải là khác và giới thiệu giả định không chính đáng trong trường hợp này nhưng không phải trường hợp khác).
masonk

12
Nếu bạn không biết, hãy kiểm tra. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Java7 jdk chắc chắn nội tuyến các phương thức được xâu chuỗi và thực hiện xung quanh cùng số lần lặp để đánh dấu void setters là nóng và nội tuyến chúng cũng vậy. Methinks bạn đánh giá thấp sức mạnh của các thuật toán cắt tỉa opcode của JVM; nếu nó biết bạn đang trả lại cái này, nó sẽ bỏ qua opcode jrs (java return statement) và chỉ để lại cái này trên stack.
Ajax

1
Andreas, tôi đồng ý nhưng vấn đề xuất hiện khi bạn có lớp trên lớp mã không hiệu quả. 99% thời gian bạn nên viết mã cho sự rõ ràng được nói rất nhiều ở đây. Nhưng cũng có những lúc bạn cần phải thực tế và sử dụng nhiều năm kinh nghiệm của mình để tối ưu hóa sớm theo nghĩa kiến ​​trúc chung.
Truyền thuyết Bước sóng

6

Đó không phải là một thực hành xấu cả. Nhưng nó không tương thích với JavaBeans Spec .

Và có rất nhiều đặc điểm kỹ thuật phụ thuộc vào những người truy cập tiêu chuẩn.

Bạn luôn có thể làm cho chúng cùng tồn tại với nhau.

public class Some {
    public String getValue() { // JavaBeans
        return value;
    }
    public void setValue(final String value) { // JavaBeans
        this.value = value;
    }
    public String value() { // simple
        return getValue();
    }
    public Some value(final String value) { // fluent/chaining
        setValue(value);
        return this;
    }
    private String value;
}

Bây giờ chúng ta có thể sử dụng chúng cùng nhau.

new Some().value("some").getValue();

Ở đây có một phiên bản khác cho đối tượng bất biến.

public class Some {

    public static class Builder {

        public Some build() { return new Some(value); }

        public Builder value(final String value) {
            this.value = value;
            return this;
        }

        private String value;
    }

    private Some(final String value) {
        super();
        this.value = value;
    }

    public String getValue() { return value; }

    public String value() { return getValue();}

    private final String value;
}

Bây giờ chúng ta có thể làm điều này.

new Some.Builder().value("value").build().getValue();

2
Chỉnh sửa của tôi đã bị từ chối, nhưng ví dụ Builder của bạn không chính xác. Đầu tiên, .value () không trả về bất cứ thứ gì và thậm chí nó không đặt sometrường. Thứ hai, bạn nên thêm một bảo vệ và thiết lập someđể nulltrong xây dựng () để Somethực sự là bất biến, nếu không bạn có thể gọi builder.value()vào dụ Builder cùng một lần nữa. Và cuối cùng, vâng, bạn có một trình xây dựng, nhưng bạn Somevẫn có một hàm tạo công khai, nghĩa là bạn không công khai ủng hộ việc sử dụng Trình tạo, tức là người dùng không biết về nó ngoài việc thử hoặc tìm kiếm một phương thức để đặt tùy chỉnh valueở tất cả.
Adowrath

@Adowrath nếu câu trả lời không chính xác, bạn nên viết câu trả lời của riêng mình, không thử và chỉnh sửa hình dạng của người khác
CalvT

1
@JinKwon Tuyệt vời. Cảm ơn! Và xin lỗi nếu tôi có vẻ thô lỗ trước đây.
Adowrath

1
@Adowrath Xin vui lòng cho bất kỳ ý kiến ​​bổ sung cho các cải tiến. FYI, tôi không phải là người từ chối chỉnh sửa của bạn. :)
Jin Kwon

1
Tôi biết rồi mà. ^^ Và cảm ơn về phiên bản mới, giờ đây nó cung cấp một trình xây dựng "Bất biến-Một số" thực sự có thể thay đổi. Và đó là một giải pháp thông minh hơn so với các nỗ lực chỉnh sửa của tôi, so sánh, làm lộn xộn mã.
Adowrath

4

Nếu bạn sử dụng cùng một quy ước trong toàn bộ applicaiton thì có vẻ ổn.

Mặt khác, nếu phần hiện có trong ứng dụng của bạn sử dụng quy ước chuẩn, tôi sẽ sử dụng nó và thêm các trình xây dựng vào các lớp phức tạp hơn

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getfat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

1
Đây chính xác là những gì mẫu Builder được thiết kế để khắc phục. Nó không phá vỡ lambdas trong Java 8, nó không phá vỡ các công cụ JavaBeans khó tính và nó không gây ra bất kỳ vấn đề tối ưu hóa nào trong JVM (vì đối tượng chỉ tồn tại trong thời gian khởi tạo). Nó cũng giải quyết vấn đề "quá nhiều nhà xây dựng" mà bạn gặp phải bằng cách đơn giản là không sử dụng các nhà xây dựng, cũng như loại bỏ ô nhiễm đống từ các lớp ẩn danh hai mặt.
ndm13

Điểm thú vị - Tôi nhận thấy rằng nếu hầu như không có gì trong lớp của bạn là bất biến, mặc dù (ví dụ: GUI có cấu hình cao), thì có lẽ bạn có thể tránh Builder hoàn toàn.
Philip Guin

4

Paulo Abrantes cung cấp một cách khác để làm cho các setters của JavaBean thành thạo: định nghĩa một lớp trình tạo bên trong cho mỗi JavaBean. Nếu bạn đang sử dụng các công cụ bị thất bại bởi các setters trả về giá trị, mẫu của Paulo có thể giúp ích.



3

Tôi ủng hộ setters có lợi nhuận "này". Tôi không quan tâm nếu nó không tuân thủ đậu. Đối với tôi, nếu có biểu thức / câu lệnh "=" thì setters trả về giá trị là ổn.


2

Tôi đã từng thích cách tiếp cận này nhưng tôi đã quyết định chống lại nó.

Lý do:

  • Dễ đọc. Nó làm cho mã dễ đọc hơn khi có mỗi setFoo () trên một dòng riêng biệt. Bạn thường đọc mã nhiều, nhiều lần so với lần duy nhất bạn viết nó.
  • Tác dụng phụ: setFoo () chỉ nên đặt trường foo, không có gì khác. Trả lại đây là thêm một "CÁI GÌ".

Mẫu Builder mà tôi thấy không sử dụng quy ước setFoo (foo) .setBar (bar) mà nhiều foo (foo) .bar (bar). Có lẽ vì chính xác những lý do đó.

Đó là, như luôn luôn là một vấn đề của hương vị. Tôi chỉ thích cách tiếp cận "ít bất ngờ nhất".


2
Tôi đồng ý về tác dụng phụ. Setters mà trả lại công cụ vi phạm tên của họ. Bạn đang thiết lập foo, nhưng bạn có nhận được một đối tượng? Đây là một đối tượng mới hay tôi đã thay đổi cái cũ?
crunchdog

2

Mẫu đặc biệt này được gọi là Phương thức Chaining. Liên kết Wikipedia , điều này có nhiều lời giải thích và ví dụ về cách nó được thực hiện trong các ngôn ngữ lập trình khác nhau.

PS: Chỉ cần nghĩ đến việc để nó ở đây, vì tôi đang tìm tên cụ thể.


1

Ngay từ cái nhìn đầu tiên: "Ghastly!".

Suy nghĩ thêm

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

thực sự ít bị lỗi hơn

Employee anEmployee = new Employee();
anEmployee.setName("xxx");
...
list.add(anEmployee);

Vì vậy, khá thú vị. Thêm ý tưởng vào túi công cụ ...


1
Không, nó vẫn còn ghê gớm. Từ góc độ bảo trì, cái sau tốt hơn bởi vì nó dễ đọc hơn. Ngoài ra, các trình kiểm tra mã tự động như CheckStyle sẽ thực thi các dòng có 80 ký tự theo mặc định - mã sẽ được bọc bằng mọi cách, kết hợp với vấn đề về khả năng đọc / bảo trì. Và cuối cùng - đó là Java; Không có lợi ích gì khi viết mọi thứ trên một dòng khi nó sẽ được biên dịch thành mã byte.
Ngựa vằn OMG

1
Cá nhân, tôi nghĩ rằng cái trước dễ đọc hơn, đặc biệt nếu bạn đang tạo một vài đối tượng theo cách này.
Ken Liu

@Ken: Viết mã một phương thức. Viết một bản sao ở định dạng trôi chảy; một bản sao khác. Bây giờ đưa hai bản sao cho một vài người, và hỏi xem bản nào họ thấy dễ đọc hơn. Đọc nhanh hơn, mã nhanh hơn.
Ngựa vằn OMG

Giống như hầu hết các công cụ, nó có thể dễ dàng sử dụng quá mức. JQuery được định hướng xung quanh kỹ thuật này và do đó dễ bị các chuỗi cuộc gọi dài mà tôi thấy thực sự làm suy yếu khả năng đọc.
staticsan

2
Sẽ ổn thôi nếu có dấu ngắt dòng trước mỗi dấu chấm và thụt lề. Sau đó, nó sẽ dễ đọc như phiên bản thứ hai. Trên thực tế nhiều hơn, vì không có dự phòng listngay từ đầu.
MauganRa

1

Vâng, tôi nghĩ đó là một ý tưởng tốt.

Nếu tôi có thể thêm một cái gì đó, thì vấn đề này là gì:

class People
{
    private String name;
    public People setName(String name)
    {
        this.name = name;
        return this;
    }
}

class Friend extends People
{
    private String nickName;
    public Friend setNickName(String nickName)
    {
        this.nickName = nickName;
        return this;
    }
}

Điều này sẽ làm việc:

new Friend().setNickName("Bart").setName("Barthelemy");

Điều này sẽ không được chấp nhận bởi Eclipse! :

new Friend().setName("Barthelemy").setNickName("Bart");

Điều này là do setName () trả về một Người chứ không phải là Bạn bè và không có PeoplesetNickName.

Làm thế nào chúng ta có thể viết setters để trả về lớp TỰF thay vì tên của lớp?

Một cái gì đó như thế này sẽ ổn (nếu từ khóa TỰF tồn tại). Điều này có tồn tại không?

class People
{
    private String name;
    public SELF setName(String name)
    {
        this.name = name;
        return this;
    }
}

1
Có một vài trình biên dịch Java khác ngoài Eclipse sẽ không chấp nhận điều đó :). Về cơ bản, bạn đang chạy hệ thống kiểu Java (là tĩnh, không động như một số ngôn ngữ kịch bản lệnh): bạn sẽ phải truyền những gì từ setName () cho một Người bạn trước khi bạn có thể đặtNickName () trên nó. Vì vậy, thật không may, đối với hệ thống phân cấp thừa kế, điều này giúp loại bỏ phần lớn lợi thế về khả năng đọc và làm cho kỹ thuật này có khả năng hữu ích khác không hữu ích.
Cornel Masson

5
Sử dụng thuốc generic. class Chainable <Self extends Chainable> {public Self doS Something () {return (Self) this;}} Đây không phải là loại an toàn về mặt kỹ thuật (nó sẽ phân lớp nếu bạn triển khai lớp với loại Self bạn có thể xử lý), nhưng nó là ngữ pháp chính xác và các lớp con sẽ trả về kiểu riêng của chúng.
Ajax

Ngoài ra: "TỰ TIN" mà Baptiste đang sử dụng được gọi là loại tự, không có trong nhiều ngôn ngữ (Scala thực sự là ngôn ngữ duy nhất tôi có thể nghĩ ra ngay bây giờ), trong khi sử dụng Generics của Ajax là cái gọi là "Thật kỳ lạ mẫu khuôn mẫu định kỳ ", cố gắng đối phó với việc thiếu kiểu tự, nhưng nó có một số nhược điểm: (từ class C<S extends C<S>>, an toàn hơn đồng bằng S extends C. a) Nếu A extends B, và B extends C<B>, và bạn có A, bạn chỉ biết rằng B được trả về trừ khi A ghi đè lên từng phương thức. b) Bạn không thể biểu thị một địa phương, không thô C<C<C<C<C<...>>>>>.
Adowrath

1

Nói chung, đó là một cách thực hành tốt, nhưng bạn có thể cần các hàm loại tập hợp sử dụng loại Boolean để xác định xem thao tác đã được hoàn thành thành công hay chưa, đó cũng là một cách. Nói chung, không có giáo điều để nói rằng điều này là tốt hay giường, nó xuất phát từ tình huống, tất nhiên.


2
Làm thế nào về việc sử dụng các ngoại lệ để chỉ ra tình trạng lỗi? Mã lỗi có thể dễ dàng bị bỏ qua vì nhiều lập trình viên C đã học rất nhiều. Các ngoại lệ có thể làm bong bóng ngăn xếp đến điểm mà chúng có thể được xử lý.
ddimitrov

Các ngoại lệ thường được ưu tiên hơn, nhưng mã lỗi cũng hữu ích khi bạn không thể sử dụng ngoại lệ.
Narek

1
Xin chào @Narek, có lẽ bạn có thể giải thích trong trường hợp nào nên sử dụng mã lỗi trong setters hơn là ngoại lệ và tại sao?
ddimitrov

0

Từ tuyên bố

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

tôi đang thấy hai điều

1) Tuyên bố vô nghĩa. 2) Thiếu khả năng đọc.


0

Điều này có thể dễ đọc hơn

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!")); 

hoặc cái này

list.add(new Employee()
          .setName("Jack Sparrow")
          .setId(1)
          .setFoo("bacon!")); 

Đây là cách dễ đọc hơn:

Employee employee = new Employee();
employee.setName("Jack Sparrow")
employee.setId(1)
employee.setFoo("bacon!")); 
list.add(employee); 

8
Tôi nghĩ nó khá dễ đọc nếu bạn không thử đặt tất cả mã của mình lên một dòng.
Ken Liu

0

Tôi đã thực hiện setters của mình khá lâu và vấn đề thực sự duy nhất là với các thư viện gắn bó với getPropertyDescriptors nghiêm ngặt để có được trình truy cập bean reader / reader. Trong những trường hợp đó, java "bean" của bạn sẽ không có các trình soạn thảo mà bạn mong đợi.

Ví dụ, tôi đã không kiểm tra chắc chắn, nhưng tôi sẽ không ngạc nhiên khi Jackson sẽ không nhận ra chúng là setters khi tạo cho bạn các đối tượng java từ json / maps. Tôi hy vọng tôi đã sai về điều này (tôi sẽ kiểm tra nó sớm).

Trong thực tế, tôi đang phát triển một ORM trung tâm SQL nhẹ và tôi phải thêm một số mã beyong getPropertyDescriptors vào các setters được công nhận trả về điều này.


0

Lâu rồi mới trả lời, nhưng hai xu của tôi ... Không sao đâu. Tôi muốn giao diện lưu loát này được sử dụng thường xuyên hơn.

Lặp lại biến 'nhà máy' không thêm thông tin bên dưới:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Foo.class);
factory.setFilter(new MethodFilter() { ...

Cái này sạch hơn, imho:

ProxyFactory factory = new ProxyFactory()
.setSuperclass(Properties.class);
.setFilter(new MethodFilter() { ...

Tất nhiên, như một trong những câu trả lời đã được đề cập, API Java sẽ phải được điều chỉnh để thực hiện điều này một cách chính xác cho một số tình huống, như kế thừa và công cụ.


0

Tốt hơn là sử dụng các cấu trúc ngôn ngữ khác nếu có. Ví dụ: trong Kotlin, bạn sẽ sử dụng với , áp dụng hoặc cho phép . Nếu sử dụng phương pháp này, bạn sẽ không thực sự cần phải trả về một thể hiện từ setter của mình.

Cách tiếp cận này cho phép mã khách hàng của bạn là:

  • Không quan tâm đến loại trả lại
  • Dễ bảo trì hơn
  • Tránh tác dụng phụ của trình biên dịch

Dưới đây là một số ví dụ.

val employee = Employee().apply {
   name = "Jack Sparrow"
   id = 1
   foo = "bacon"
}


val employee = Employee()
with(employee) {
   name = "Jack Sparrow"
   id = 1
   foo = "bacon"
}


val employee = Employee()
employee.let {
   it.name = "Jack Sparrow"
   it.id = 1
   it.foo = "bacon"
}

0

Nếu tôi đang viết API, tôi sử dụng "return this" để đặt các giá trị sẽ chỉ được đặt một lần. Nếu tôi có bất kỳ giá trị nào khác mà người dùng sẽ có thể thay đổi, tôi sử dụng bộ set void tiêu chuẩn thay thế.

Tuy nhiên, theo tôi thì đó thực sự là một vấn đề ưu tiên và xâu chuỗi trông khá tuyệt vời.


0

Tôi đồng ý với tất cả các áp phích tuyên bố rằng điều này phá vỡ thông số JavaBeans. Có nhiều lý do để bảo tồn điều đó, nhưng tôi cũng cảm thấy rằng việc sử dụng Mẫu xây dựng này (đã được ám chỉ) có vị trí của nó; miễn là nó không được sử dụng ở mọi nơi, nó sẽ được chấp nhận. "Đó là nơi", đối với tôi, là điểm cuối là một cuộc gọi đến phương thức "build ()".

Tất nhiên có nhiều cách khác để thiết lập tất cả những điều này, nhưng ưu điểm ở đây là nó tránh được 1) các hàm tạo công cộng nhiều tham số và 2) các đối tượng được chỉ định một phần. Ở đây, bạn có trình xây dựng thu thập những gì cần thiết và sau đó gọi "build ()" của nó ở cuối, điều này có thể đảm bảo rằng một đối tượng được chỉ định một phần không được xây dựng, vì hoạt động đó có thể được hiển thị ít hơn công khai. Giải pháp thay thế sẽ là "các đối tượng tham số", nhưng IMHO chỉ đẩy vấn đề trở lại một cấp.

Tôi không thích các hàm tạo nhiều tham số vì chúng làm cho nhiều khả năng có nhiều đối số cùng loại được truyền vào, điều này có thể giúp dễ dàng chuyển các đối số sai sang tham số. Tôi không thích sử dụng nhiều setters vì đối tượng có thể được sử dụng trước khi nó được cấu hình đầy đủ. Hơn nữa, khái niệm có các giá trị mặc định dựa trên các lựa chọn trước đó được phục vụ tốt hơn với phương thức "build ()".

Tóm lại, tôi nghĩ rằng đó là một thực hành tốt, nếu được sử dụng đúng cách.


-4

Thói quen xấu: setter set getter get

những gì về việc khai báo một phương thức rõ ràng, điều đó làm cho U

setPropertyFromParams(array $hashParamList) { ... }

không tốt cho việc tự động hoàn thành và tái cấu trúc và khả năng đọc mã nồi hơi anb
Andreas Dietrich

Bởi vì: 1) Bạn phải nhớ thứ tự hoặc đọc nó từ tài liệu, 2) bạn mất hết an toàn loại thời gian biên dịch, 3) bạn phải truyền các giá trị (điều này và 2) tất nhiên không phải là vấn đề trong động ngôn ngữ tất nhiên), 4) bạn không thể mở rộng điều này một cách hữu ích ngoài việc kiểm tra độ dài mảng trên mỗi lần gọi và 5) nếu bạn thay đổi bất cứ điều gì, có thể là thứ tự hoặc thậm chí là sự tồn tại của các giá trị nhất định, bạn không thể kiểm tra xem cả thời gian chạy cũng không biên dịch nghi ngờ thời gian nào cả . Với setters: 1), 2), 3): Không thành vấn đề. 4) Thêm phương thức mới không phá vỡ bất cứ điều gì. 5) thông báo lỗi rõ ràng.
Adowrath
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.