세터가 "this"를 반환하는 것은 나쁜 습관입니까?


249

자바의 setter가 "this"를 반환하게하는 것이 좋은 생각입니까?

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

이 패턴은 다음과 같이 setter를 연결할 수 있기 때문에 유용 할 수 있습니다.

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

이 대신에 :

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

...하지만 일종의 표준 규칙에 위배됩니다. 세터가 다른 것을 유용하게 만들 수 있기 때문에 가치가 있다고 생각합니다. 이 패턴이 JMock, JPA와 같은 일부 장소를 사용하는 것을 보았지만 드물게 보이며 일반적 으로이 패턴이 어디서나 사용되는 매우 잘 정의 된 API에만 사용됩니다.

최신 정보:

내가 설명한 것은 분명히 유효하지만 실제로 찾고있는 것은 이것이 일반적으로 수용 가능한지 여부와 함정이나 관련 모범 사례가 있는지에 대한 생각입니다. 빌더 패턴에 대해 알고 있지만 설명하고있는 것보다 조금 더 관련이 있습니다. Josh Bloch가 설명했듯이 객체 생성을 위해 관련된 정적 빌더 클래스가 있습니다.


1
이 디자인 패턴을 한참 전에 보았으므로 가능한 한이 작업을 수행합니다. 메소드가 작업을 수행하기 위해 명시 적으로 무언가를 리턴 할 필요가없는 경우 이제는 메소드를 리턴합니다 this. 때로는 값을 반환하는 대신 객체의 멤버에서 작동하도록 함수를 변경하여이를 수행 할 수도 있습니다. 훌륭합니다. :)
Inversus

4
자체 및 빌더에서 리턴하는 텔레스코픽 세터의 경우 setName (String name) 대신 withName (String name)을 사용하는 것이 좋습니다. setter에 대한 일반적인 관행과 기대는 void를 반환하는 것입니다. "비표준"세터는 기존 프레임 워크 (예 : JPA 엔티티 관리자, Spring 등)와 잘 작동하지 않을 수 있습니다.
oᴉɹǝɥɔ

각 호출 전에 줄 바꿈을 소개하십시오 :) 그리고 IDE를 구성하거나 이것을 존중하지 않으면 적절한 것을 찾으십시오.
MauganRa

널리 사용되는 프레임 워크 (봄과 예를 들어 Hibernate는) 엄격 무효 족 규칙을 준수 (적어도 그들에 사용됩니다)
Legna

답변:


83

나는 그것에 특별히 문제가 있다고 생각하지 않습니다. 그것은 단지 스타일의 문제입니다. 다음과 같은 경우에 유용합니다.

  • 한 번에 많은 필드를 설정해야합니다 (건설시 포함).
  • 코드를 작성할 때 어떤 필드를 설정해야하는지 알고
  • 필드를 설정하려는 여러 가지 조합이 있습니다.

이 방법의 대안은 다음과 같습니다.

  1. 하나의 메가 생성자 (단점 : 많은 null 또는 기본값을 전달할 수 있으며 어떤 값이 무엇에 해당하는지 알기가 어렵습니다)
  2. 오버로드 된 여러 생성자 (단점 : 몇 개 이상 있으면 다루기 힘들다)
  3. 팩토리 / 정적 메소드 (단점 : 오버로드 된 생성자와 동일-몇 개 이상 있으면 다루기 힘들다)

한 번에 몇 가지 속성 만 설정하려는 경우 'this'를 반환 할 가치가 없다고 말하고 싶습니다. 나중에 상태 / 성공 표시기 / 메시지와 같은 다른 것을 반환하기로 결정하면 확실히 떨어집니다.


1
글쎄, 일반적으로 어쨌든 세터에서 어떤 것도 반환하지 않습니다.
Ken Liu

17
어쩌면 시작하지는 않지만 세터가 반드시 원래의 목적을 유지하지는 않습니다. 변수였던 변수는 여러 변수를 포함하거나 다른 부작용이있는 상태로 변경 될 수 있습니다. 일부 세터는 이전 값을 반환하고 다른 세터는 예외에 대해 실패가 너무 일반적인 경우 실패 표시기를 반환 할 수 있습니다. 그래도 흥미로운 점이 하나 더 있습니다. 사용중인 도구 / 프레임 워크가 반환 값이있을 때 세터를 인식하지 못하면 어떻게됩니까?
Tom Clift

12
@Tom good point, 이렇게하면 getter와 setter에 대한 "Java bean"규칙이 깨집니다.
Andy White

2
@TomClift "Java Bean"규칙을 위반하면 문제가 발생합니까? "Java Bean"규칙을 사용하는 라이브러리는 리턴 유형을 보거나 메소드 매개 변수 및 메소드 이름 만 찾습니다.
Theo Briscoe

3
그렇기 때문에 빌더 패턴이 존재하고 세터가 무언가를 반환해서는 안되며 대신 필요한 모양이 더 좋고 코드가 덜 필요한 경우 빌더를 작성해야합니다.
RicardoE

106

나쁜 습관이 아닙니다. 점점 일반적인 관행입니다. 원하지 않는 경우 대부분의 언어는 반환 된 객체를 처리 할 필요가 없으므로 "일반적인"세터 사용법 구문을 변경하지 않지만 세터를 서로 연결할 수 있습니다.

이것을 일반적으로 빌더 패턴 또는 유창한 인터페이스 라고합니다 .

Java API에서도 일반적입니다.

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

26
그것은 종종있어 사용 업체에서,하지만 난 "이 ... 빌더 패턴라고"언급하지 않았다.
Laurence Gonsalves

10
유창한 인터페이스의 근거 중 일부는 코드를보다 쉽게 ​​읽을 수 있다는 것입니다. 쓰기가 더 편리하다는 것을 알 수 있었지만 읽기가 더 어려워졌습니다. 그것이 내가 가진 유일한 불일치입니다.
Brent Writes Code

30
열차 잔해 방지 패턴이라고도합니다. 문제는 널 포인터 예외 스택 추적에 이와 같은 행이 포함되어 있으면 어떤 호출이 널을 리턴했는지 전혀 알 수 없다는 것입니다. 즉, 체인은 모든 비용으로 피해야하지만 나쁜 라이브러리 (예 : 집에서 만든)를 조심해야합니다.
ddimitrov

18
@ddimitrov 당신 이것을 반환하도록 제한하는 한 그것은 결코 문제가되지 않을 것입니다 (첫 번째 호출 만 NPE를 던질 수 있습니다)
Stefan

4
가독성이 좋지 않은 곳에 줄 바꿈과 들여 쓰기를했다고 가정하고 읽고 쓰는 것이 더 쉽습니다 ! ( bla.foo.setBar1(...) ; bla.foo.setBar2(...)당신이 쓸 수있을 때 와 같이 반복되는 코드의 중복 혼란을 피하기 때문에 bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...)(SO 주석에서 줄 바꿈을 사용할 수는 없습니다 :-( ... 10 명의 setter 또는 더 복잡한 호출을 고려할 수 있기를 바랍니다))
Andreas Dietrich

90

요약:

  • 이를 "유창한 인터페이스"또는 "메소드 체인"이라고합니다.
  • 이것은 "표준"Java는 아니지만 요즘 더 많이 볼 수 있지만 (jQuery에서 잘 작동합니다)
  • JavaBean 스펙을 위반하므로 다양한 도구 및 라이브러리, 특히 JSP 빌더 및 Spring과 충돌합니다.
  • JVM이 정상적으로 수행하는 일부 최적화를 방해 할 수 있습니다.
  • 어떤 사람들은 코드를 정리한다고 생각하고 어떤 사람들은 "거대한"생각

언급되지 않은 몇 가지 다른 점 :

  • 이는 각 기능이 한 가지만 수행해야한다는 원칙에 위배됩니다. 이것을 믿거 나 믿지 않을 수도 있지만 Java에서는 잘 작동한다고 생각합니다.

  • IDE는 기본적으로이를 생성하지 않습니다.

  • 마지막으로 여기 실제 데이터 포인트가 있습니다. 이와 같은 라이브러리를 사용하는 데 문제가 있습니다. Hibernate의 쿼리 빌더는 기존 라이브러리에서 이에 대한 예입니다. Query의 set * 메소드가 쿼리를 리턴하므로 서명을 사용하여 사용법을 알려주는 것만으로는 불가능합니다. 예를 들면 다음과 같습니다.

    Query setWhatever(String what);
  • 모호성을 소개합니다 : 메소드가 현재 객체 (패턴)를 수정합니까, 아니면 Query가 실제로 불변 (매우 인기 있고 귀중한 패턴)이며 메소드가 새로운 것을 반환합니다. 라이브러리를 사용하기 어렵게 만들고 많은 프로그래머가이 기능을 이용하지 않습니다. 세터가 세터 인 경우 사용 방법이 더 명확합니다.


5
btw, "유체"가 아닌 "유창한"언어로 말하면 언어와 같은 일련의 메소드 호출을 구성 할 수 있습니다.
Ken Liu

1
불변성에 대한 마지막 요점은 매우 중요합니다. 가장 간단한 예는 문자열입니다. Java 개발자는 String에서 메소드를 사용할 때 완전히 새로운 인스턴스를 얻지 만 동일하지만 수정 된 인스턴스를 얻지 못할 것으로 기대합니다. 유창한 인터페이스를 사용하면 메소드 문서에서 반환 된 객체가 새 인스턴스 대신 'this'라는 것을 언급해야합니다.
MeTTeO

7
나는 전반적으로 동의하지만 이것이 "한 가지 일만"원칙을 위반한다는 것에 동의하지 않습니다. 귀환 this은 복잡한 로켓 과학이 아닙니다. :-)
user949300

추가 사항 : 또한 명령-쿼리 분리 원칙을 위반합니다.
Marton_hun

84

나는 이것을 위해 'with'방법을 사용하는 것을 선호합니다 :

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

그러므로:

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

경고 :이 withX구문은 일반적으로 불변 개체에 "세터"를 제공하는 데 사용되므로 이러한 메서드를 호출하면 기존 인스턴스를 변경하지 않고 새 개체를 만들 것으로 기대할 수 있습니다. 아마도 더 합리적인 표현은 다음과 같습니다.

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

chainsetXyz () 명명 규칙을 사용하면 거의 모든 사람이 행복해야합니다.


18
+1 재미있는 컨벤션. 이제는 모든 클래스 필드에 대해 get, a set및 a 가 있어야하는 것처럼 보이기 때문에 내 코드에서 채택하지 않을 것 with입니다. 그래도 여전히 흥미로운 해결책입니다. :)
Paul Manta

1
세터를 얼마나 자주 호출하는지에 따라 다릅니다. 그 세터가 많이 호출되면 다른 곳의 코드를 단순화하기 때문에 추가하는 것이 더 가치가 있다는 것을 알았습니다. YMMV
qualidafial

2
당신이 추가 만약 Project Lombok@Getter/@Setter주석 ... 그 체인에 대한 환상적 일 것입니다. 또는 JQuery와 Javascript 친구가 사용하는 결합기Kestrel ( github.com/raganwald/Katy ) 와 같은 것을 사용할 수 있습니다.
Ehtesh Choudhury 2014

4
@ AlikElzin-kilaka 사실 방금 Java 8의 java.time 불변 클래스가이 패턴을 사용한다는 것을 알았습니다. 예 : LocalDate.withMonth, withYear 등
qualidafial

4
with접두사는 다른 규칙입니다. @qualidafial이 예를 들었 듯이. 접두사가 붙은 메소드 는 현재 인스턴스와 같은 새 인스턴스를 with반환 this하지만 with그 변경 은 반환하지 않아야합니다 . 이것은 객체를 불변으로 만들고 싶을 때 수행됩니다. 그래서 접두사가 붙은 메소드를 볼 때 with동일한 객체가 아닌 새로운 객체를 얻을 것이라고 가정합니다.
tempcke

26

'this'setter에서 돌아 가지 않고 두 번째 옵션을 사용하지 않으려면 다음 구문을 사용하여 속성을 설정할 수 있습니다.

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

제쳐두고 C #에서는 약간 깨끗하다고 ​​생각합니다.

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

16
이중 중괄호 초기화는 익명의 내부 클래스를 생성하므로 equals에 문제가있을 수 있습니다. 참조 c2.com/cgi/wiki?DoubleBraceInitialization
Csaba_H

@Csaba_H 분명히 문제는 방법을 망친 사람의 잘못이라는 것입니다 equals. equals자신이하는 일을 알면 익명 클래스를 처리하는 매우 깨끗한 방법 이 있습니다.
AJMansfield

그것을 위해 새로운 (익명) 클래스를 만드는가? 모든 경우에 대해?
user85421

11

getter / setter의 관례를 위반할뿐만 아니라 Java 8 메소드 참조 프레임 워크도 손상시킵니다. MyClass::setMyValueA는 BiConsumer<MyClass,MyValue>, 그리고 myInstance::setMyValueA는 Consumer<MyValue>. setter return이있는 경우 this더 이상 유효한 인스턴스가 Consumer<MyValue>아닌의 인스턴스이며 Function<MyValue,MyClass>void 설정이라고 가정하면 해당 setter에 대한 메소드 참조를 사용하는 모든 항목이 중단됩니다.


2
Java가 JVM뿐만 아니라 반환 유형별로 과부하 할 수있는 방법이 있다면 좋을 것입니다. 이러한 주요 변경 사항을 쉽게 무시할 수 있습니다.
Adowrath

1
당신은 항상 모두를 확장하는 기능 인터페이스를 정의 할 수 있습니다 Consumer<A>Function<A,B>의 기본 구현을 제공함으로써 void accept(A a) { apply(a); }. 그런 다음 쉽게 사용할 수 있으며 특정 형식이 필요한 코드를 깨지 않습니다.
Steve K

아아! 이것은 명백한 잘못입니다! 이를 공극 호환성이라고합니다. 값을 반환하는 setter는 소비자 역할을 할 수 있습니다. ideone.com/ZIDy2M
Michael

8

Java를 모르지만 C ++ 에서이 작업을 수행했습니다. 다른 사람들은 그것이 줄을 정말로 길고 읽기가 어렵게한다고 말했지만, 나는 이것을 여러 번 그렇게했습니다.

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

이것은 더 낫습니다 :

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

적어도 나는 생각합니다. 그러나 당신은 저를 downvote하고 당신이 원한다면 나에게 끔찍한 프로그래머라고 불러도 좋습니다. 그리고 당신이 Java로 이것을 할 수 있는지 모르겠습니다.


"더 나은"기능은 최신 IDE에서 사용할 수있는 포맷 소스 코드 기능에는 적합하지 않습니다. 운수 나쁘게.
Thorbjørn Ravn Andersen

아마 당신이 맞을 것입니다 ... 내가 사용한 유일한 자동 포맷터는 emacs의 자동 들여 쓰기입니다.
카슨 마이어스

2
소스 코드 포맷터는 체인에서 각 메소드 호출 후 간단한 //로 강제 될 수 있습니다. 그것은 코드를 조금 못 생겼지 만 세로로 된 일련의 문장이 가로로 다시 포맷되는 것은 아닙니다.
qualidafial

@qualidafial //이미 줄 바꿈 된 줄을 연결하지 않도록 IDE를 구성한 경우 각 방법을 수행 할 필요가 없습니다 (예 : Eclipse> 속성> Java> 코드 스타일> 포맷터> 줄 줄 바꿈> 이미 줄 바꿈 된 줄을 연결하지 마십시오).
DJDaveMark

6

'유창한 인터페이스'라고하는이 체계 (펀칭 된 의도)는 현재 매우 인기가 있습니다. 괜찮지 만 실제로 차 한잔은 아닙니다.


6

void를 반환하지 않기 때문에 더 이상 유효한 JavaBean 속성 설정자가 아닙니다. 시각적 "Bean Builder"도구를 사용하는 세계 7 명 중 하나이거나 JSP-bean-setProperty 요소를 사용하는 17 명 중 하나 인 경우에는 문제가 될 수 있습니다.


Spring과 같은 Bean 인식 프레임 워크를 사용하는 경우에도 중요합니다.
ddimitrov

6

적어도 이론상 ,이 호출 사이에 거짓 종속성을 설정하여 JVM의 최적화 메커니즘을 손상시킬 수 있습니다.

그것은 구문 설탕이어야하지만 실제로는 지능이 뛰어난 Java 43의 가상 머신에서 부작용을 일으킬 수 있습니다.

그렇기 때문에 투표하지 않습니다. 사용하지 마십시오.


10
흥미 롭군요 ... 조금 더 확장 해 주시겠습니까?
Ken Liu

3
슈퍼 스칼라 프로세서가 병렬 실행을 처리하는 방법에 대해 생각하십시오. 두 번째 set방법 을 실행할 객체 set는 프로그래머가 알고 있지만 첫 번째 방법에 의존 합니다.
Marian

2
나는 여전히 따르지 않는다. 두 개의 별도 명령문으로 Foo를 설정 한 다음 Bar를 설정하면 Bar를 설정하는 오브젝트의 상태가 Foo를 설정하는 오브젝트와 다릅니다. 따라서 컴파일러는 해당 명령문을 병렬화 할 수 없었습니다. 적어도, 나는 부주의 한 가정을 도입하지 않고 어떻게 그것이 가능한지 알지 못합니다. 한 경우에는 부주의 한 가정이지만 다른 경우는 아닙니다.
masonk

12
모르는 경우 테스트하십시오. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining java7 jdk는 체인 메소드를 확실히 인라인하고, void setter를 hot으로 표시하고 인라인으로 표시하는 데 동일한 반복 횟수를 수행합니다. Methinks는 JVM의 opcode 정리 알고리즘의 성능을 과소 평가합니다. 이것이 당신이 이것을 알고 있다는 것을 알고 있다면, 그것은 jrs (java return statement) opcode를 건너 뛰고 스택에 남겨 둡니다.
Ajax

1
Andreas, 나는 동의하지만 비효율적 인 코드 레이어에 레이어가있을 때 문제가 나타납니다. 시간의 99 %는 명확성을 위해 코딩해야하며 여기에서 많이 언급됩니다. 그러나 사실적이고 현실적인 경험을 바탕으로 일반적인 아키텍처 의미에서 조기에 최적화해야하는 경우도 있습니다.
LegendLength

6

전혀 나쁜 습관이 아닙니다. 그러나 JavaBeans Spec 과 호환되지 않습니다 .

그리고 이러한 표준 접근 자에 따라 많은 사양이 있습니다.

항상 서로 공존 할 수 있습니다.

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;
}

이제 함께 사용할 수 있습니다.

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

불변의 객체를위한 다른 버전이 있습니다.

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;
}

이제 우리는 이것을 할 수 있습니다.

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

2
편집 내용이 거부되었지만 빌더 예제가 올바르지 않습니다. 먼저 .value ()는 아무것도 반환하지 않으며 some필드를 설정하지도 않습니다 . 둘째, 보호 기능을 추가하고 build ()로 설정 some해야 null하므로 Some변경할 수 없습니다. 그렇지 않으면 builder.value()동일한 빌더 인스턴스를 다시 호출 할 수 있습니다. 마지막으로, Some그래도 빌더가 있지만 여전히 공용 생성자가 있습니다. 즉, 빌더 사용을 공개적으로 옹호하지 않습니다. 즉, 사용자는 사용자 정의를 설정하는 방법을 시도하거나 검색하는 것 외에 다른 것을 알고 있지 않습니다. value조금도.
Adowrath

@Adowrath 답변이 정확하지 않은 경우 자신의 답변을 작성하고 다른 사람의 모습을 수정하려고 시도해서는 안됩니다
CalvT

1
@JinKwon Great. 감사! 내가 전에 무례하게 보였다면 미안합니다.
Adowrath

1
@Adowrath 향상된 기능에 대한 추가 의견이 있으면 언제든지 문의하십시오. 참고로, 저는 귀하의 편집을 거부 한 사람이 아닙니다. :)
Jin Kwon

1
내가 알지. ^^ 새로운 버전 덕분에 이제는 변경 가능한 "Immutable-Some"빌더를 제공합니다. 그리고 그것은 코드를 혼란스럽게 만드는 편집 시도보다 더 영리한 솔루션입니다.
Adowrath

4

전체 응용 프로그램에서 동일한 규칙을 사용하면 괜찮습니다.

어쨌든 응용 프로그램의 기존 부분이 표준 규칙을 사용하면 더 복잡한 클래스에 빌더를 추가합니다.

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
이것이 바로 빌더 패턴이 수정하도록 설계된 것입니다. Java 8에서 람다를 끊지 않고 까다로운 JavaBeans 도구를 손상시키지 않으며 JVM에서 최적화 문제를 일으키지 않습니다 (객체가 인스턴스화 중에 만 존재하기 때문에). 또한 단순히 빌더를 사용하지 않고 이중 중괄호 익명 클래스에서 힙 오염을 제거함으로써 발생하는 "너무 많은 생성자"문제를 해결합니다.
ndm13

흥미로운 점-클래스의 거의 아무것도 변경할 수없는 경우 (예 : 고도로 구성 가능한 GUI) 빌더를 완전히 피할 수 있음을 알았습니다.
Philip Guin

4

Paulo Abrantes 는 JavaBean 세터를 유창하게 만드는 또 다른 방법을 제공합니다. 각 JavaBean에 대한 내부 빌더 클래스를 정의하십시오. 값을 반환하는 세터에 의해 플럭 소화 된 툴을 사용하는 경우 Paulo의 패턴이 도움이 될 수 있습니다.


2
@ cdunn2001, 경유 기계 사용, Luke
msangel

3

나는 "this"리턴을 가진 세터에 찬성한다. 콩과 호환되지 않더라도 상관 없습니다. 나에게 "="표현식 / 문을 사용하는 것이 괜찮다면 값을 반환하는 세터가 좋습니다.


2

나는이 접근 방식을 선호했지만 반대하기로 결정했다.

원인:

  • 가독성. 각 setFoo ()를 별도의 줄에 갖도록 코드를 더 읽기 쉽게 만듭니다. 일반적으로 코드를 작성할 때보 다 코드를 여러 번 더 많이 읽습니다.
  • 부작용 : setFoo ()는 필드 foo 만 설정해야합니다. 이것을 돌려주는 것은 "WHAT was that"입니다.

내가 본 빌더 패턴은 setFoo (foo) .setBar (bar) 규칙을 사용하지 않고 더 많은 foo (foo) .bar (bar)를 사용합니다. 아마도 그 이유 때문일 것입니다.

항상 맛의 문제입니다. 나는 "최소한의 놀라움"접근법을 좋아한다.


2
나는 부작용에 동의합니다. 물건을 반품하는 세터는 이름을 위반합니다. foo를 설정하고 있지만 객체를 다시 얻습니까? 이것이 새로운 물체입니까, 아니면 오래된 물체를 변경 했습니까?
crunchdog

2

이 특정 패턴을 Method Chaining이라고합니다. Wikipedia link 에는 다양한 프로그래밍 언어에서 어떻게 수행되는지에 대한 자세한 설명과 예제가 있습니다.

추신 : 특정 이름을 찾고 있었기 때문에 여기에 남겨 두는 것을 생각했습니다.


1

첫눈에 : "거대한!".

더 생각에

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

실제로보다 오류가 덜 발생합니다

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

매우 흥미 롭습니다. 툴백에 아이디어 추가 ...


1
아뇨, 여전히 굉장합니다. 유지 관리 측면에서 보면 후자가 읽기 쉽기 때문에 더 좋습니다. 또한 CheckStyle과 같은 자동 코드 체커는 기본적으로 줄을 80 자로 설정합니다. 코드는 어쨌든 줄 바꿈되어 가독성 / 유지 보수 문제가 복잡해집니다. 그리고 마지막으로-그것은 자바입니다. 어쨌든 바이트 코드로 컴파일 될 때 한 줄에 모든 것을 쓰는 것은 아무런 이점이 없습니다.
OMG Ponies

1
개인적으로, 나는 당신이 특히 여러 가지 객체를 이런 식으로 만드는 경우 전자가 읽기 쉽다고 생각합니다.
Ken Liu

@Ken : 메소드를 코딩하십시오. 하나의 사본을 유창한 형식으로 작성하십시오. 다른 하나의 다른 사본. 이제 두 사본을 두 사람에게주고 어떤 책을 더 쉽게 읽을 수 있는지 물어보십시오. 더 빨리 읽고, 더 빨리 코딩합니다.
OMG Ponies

대부분의 도구와 마찬가지로 사용하기 쉽습니다. JQuery는이 기술에 중점을두고 있으므로 실제로는 가독성을 손상시키는 것으로 알려진 긴 콜 체인에 취약합니다.
staticsan

2
각 점 앞에 줄 바꿈이 있으면 들여 쓰기 만하면됩니다. 그런 다음 두 번째 버전만큼 읽을 수 있습니다. list처음 에는 중복이 없기 때문에 실제로 더 있습니다.
MauganRa

1

예, 좋은 생각이라고 생각합니다.

무언가를 추가 할 수 있다면이 문제는 어떻습니까?

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;
    }
}

이것은 작동합니다 :

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

이것은 이클립스에서 받아 들여지지 않을 것이다! :

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

setName ()은 친구가 아닌 사람을 반환하고 PeoplesetNickName이 없기 때문입니다.

클래스 이름 대신 SELF 클래스를 반환하도록 setter를 작성하는 방법은 무엇입니까?

이와 같은 것이 좋습니다 (SELF 키워드가 존재하는 경우). 어쨌든 존재합니까?

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

1
Eclipse를 제외하고 다른 Java 컴파일러가 몇 가지 있습니다. :) 기본적으로 Java 유형 시스템 (일부 스크립팅 언어와 같이 동적이 아닌 정적)을 실행하고 있습니다. setNickName ()을 설정하기 전에 setName ()에서 나오는 내용을 Friend로 캐스팅해야합니다. 불행히도 상속 계층의 경우 가독성 이점을 많이 제거하고 잠재적으로 유용한이 기술을 유용하지 않게 만듭니다.
Cornel Masson 2018 년

5
제네릭을 사용하십시오. class Chainable <Self extends Chainable> {public Self doSomething () {return (Self) this;}} 기술적으로 안전하지는 않습니다 (자체 유형으로 클래스를 구현하면 클래스 캐스트됩니다). 올바른 문법이며 하위 클래스는 자체 유형을 반환합니다.
Ajax

또한 : Baptiste가 사용하고있는이 "SELF"는 많은 언어로 제공되지 않는 자체 유형이라고합니다 (Scala는 현재 제가 생각할 수있는 유일한 언어입니다). Ajax의 Generics 사용은 "Curiously 되풀이되는 템플릿 패턴 "을 사용하면 자체 유형의 부족에 대처하려고 시도하지만 단점이 있습니다. (에서 class C<S extends C<S>>, 일반보다 안전합니다 S extends C. a) A extends B, 및 B extends C<B>이고 A가있는 경우 A가 각각의 모든 메소드를 대체하지 않는 한 B가 리턴됩니다. b) 로컬이 아닌 로컬을 나타낼 수 없습니다 C<C<C<C<C<...>>>>>.
Adowrath

1

일반적으로 좋은 습관이지만, set-type 함수가 Boolean type을 사용하여 작업이 성공적으로 완료되었는지 여부를 결정해야 할 수도 있습니다. 일반적으로 이것이 좋거나 침대라고 말하는 교리가 없으며 물론 상황에서 비롯됩니다.


2
오류 조건을 나타 내기 위해 예외를 사용하는 것은 어떻습니까? 많은 C 프로그래머가 고통스럽게 배운 것처럼 오류 코드는 쉽게 무시할 수 있습니다. 예외는 스택을 처리 할 수있는 지점까지 버블 링 할 수 있습니다.
ddimitrov

일반적으로 예외가 바람직하지만 예외를 사용할 수없는 경우 오류 코드도 유용합니다.
Narek

1
@Narek 안녕하세요, 어쩌면 예외보다는 왜 세터에서 오류 코드를 사용하는 것이 바람직한 지 자세히 설명 할 수 있습니까?
ddimitrov

0

진술에서

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

나는 두 가지를보고있다

1) 무의미한 진술. 2) 가독성 부족.


0

읽기 어려울 수 있습니다

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

아니면 이거

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

이것은 다음보다 읽기 쉽습니다.

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

8
모든 코드를 한 줄에 넣지 않으면 읽을 수 있다고 생각합니다.
Ken Liu

0

나는 오랫동안 setter를 만들고 있었고 유일한 진짜 문제는 엄격한 getPropertyDescriptors를 사용하여 bean reader / writer bean 접근자를 얻는 라이브러리와 관련이 있습니다. 이 경우, Java "bean"에는 예상 한 writter가 없습니다.

예를 들어, 확실하게 테스트하지는 않았지만 Jackson이 json / maps에서 Java 객체를 만들 때 Jackson이 설정자를 인식하지 못한다는 사실에 놀라지 않습니다. 나는 이것에 대해 잘못되기를 바랍니다 (곧 테스트 할 것입니다).

사실, 나는 가벼운 SQL 중심 ORM을 개발 중이며 이것을 반환하는 인식 된 setter에 getPropertyDescriptors 코드를 추가해야합니다.


0

오래 전에 대답했지만 내 센트는 ... 괜찮습니다. 이 유창한 인터페이스가 더 자주 사용되기를 바랍니다.

'factory'변수를 반복해도 아래에 더 많은 정보가 추가되지 않습니다.

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

이것은 더 깨끗합니다.

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

물론 이미 언급 한 답변 중 하나 인 상속 및 도구와 같은 일부 상황 에서이 작업을 올바르게 수행하려면 Java API를 조정해야합니다.


0

가능한 경우 다른 언어 구문을 사용하는 것이 좋습니다. 예를 들어 Kotlin에서는 with , apply 또는 let을 사용 합니다. 이 방법을 사용하는 경우 실제로 setter에서 인스턴스를 반환 할 필요 가 없습니다 .

이 접근 방식을 통해 클라이언트 코드는 다음과 같습니다.

  • 반환 유형에 무관심
  • 손쉬운 유지
  • 컴파일러 부작용 방지

여기 몇 가지 예가 있어요.

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

API를 작성하는 경우 "return this"를 사용하여 한 번만 설정되는 값을 설정하십시오. 사용자가 변경할 수있는 다른 값이 있으면 표준 void setter를 대신 사용합니다.

그러나 그것은 실제로 선호의 문제이며 체인 세터는 상당히 시원하게 보입니다.


0

JavaBeans 사양을 위반한다고 주장하는 모든 포스터에 동의합니다. 그것을 보존해야 할 이유가 있지만, 나는 또한이 건축 자 패턴의 사용이 그 자리에 있다고 생각합니다. 모든 곳에서 사용되지 않는 한 수용 가능해야합니다. "장소"는 엔드 포인트가 "build ()"메서드를 호출하는 곳입니다.

물론 이러한 모든 것들을 설정하는 다른 방법이 있지만 여기서 장점은 1) 많은 매개 변수 공용 생성자와 2) 부분적으로 지정된 객체를 피한다는 것입니다. 여기에서 빌더가 필요한 것을 수집 한 후 마지막에 "build ()"를 호출하여 부분적으로 지정된 오브젝트가 구성되지 않도록 할 수 있습니다. 그 조작은 공개보다 덜 가시성이 부여 될 수 있기 때문입니다. 대안은 "매개 변수 객체"이지만, IMHO는 문제를 한 단계 뒤로 밀어냅니다.

많은 매개 변수 생성자를 싫어합니다. 많은 동일한 유형의 인수가 전달 될 가능성이 높기 때문에 잘못된 인수를 매개 변수에 쉽게 전달할 수 있습니다. 객체를 완전히 구성하기 전에 사용할 수 있기 때문에 많은 setter를 사용하는 것을 좋아하지 않습니다. 또한 이전 선택에 따라 기본값을 설정한다는 개념은 "build ()"메서드를 사용하는 것이 좋습니다.

요컨대 제대로 사용한다면 좋은 습관이라고 생각합니다.


-4

나쁜 습관 : 세터가 게터를 얻는다

메소드를 명시 적으로 선언하면 어떨까요?

setPropertyFromParams(array $hashParamList) { ... }

자동 완성 및 리팩토링과 가독성 ANB의 상용구 코드에 대한 좋지
안드레아스 디트리히

1) 순서를 기억하거나 문서에서 읽어야합니다 .2) 모든 컴파일 타임 유형 안전을 느슨하게합니다 .3) 값을 캐스팅해야합니다 (이와 2)는 물론 역동적 인 문제가 아닙니다. 물론 언어), 4) 당신은 할 수 없습니다 ) 모든 호출에 배열의 길이를 확인하는 것보다이 유용하게 다른 확장, 5 있다면 당신은 아무것도 변경은 주문하거나 특정 값도 존재 할, 당신은 할 수 없는 것도 런타임 확인 의심의 여지없이 시간을 컴파일하지 마십시오 . 세터 사용 : 1), 2), 3) : 문제 없습니다. 4) 새로운 방법을 추가해도 아무런 문제가 없습니다. 5) 명시적인 오류 메시지.
Adowrath
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.