체인 세터가 왜 전통적인가?


46

Bean에 체인을 구현하면 생성자, 메가 생성자, 팩토리를 오버로드 할 필요가없고 가독성이 향상됩니다. 객체를 불변으로 만들고 싶지 않다면 어떤 단점도 생각할 수 없습니다 .이 경우 어쨌든 setter가 없습니다. 그렇다면 이것이 OOP 규칙이 아닌 이유가 있습니까?

public class DTO {

    private String foo;
    private String bar;

    public String getFoo() {
         return foo;
    }

    public String getBar() {
        return bar;
    }

    public DTO setFoo(String foo) {
        this.foo = foo;
        return this;
    }

    public DTO setBar(String bar) {
        this.bar = bar;
        return this;
    }

}

//...//

DTO dto = new DTO().setFoo("foo").setBar("bar");

32
자바가 세터가 인간에게 가증 한 것이 아닌 유일한 언어 일 수 있기 때문에 ...
Telastyn

11
반환 값이 의미 적으로 의미가 없기 때문에 잘못된 스타일입니다. 오해의 소지가 있습니다. 유일한 단점은 키 입력을 최소화하는 것입니다.
usr

58
이 패턴은 전혀 드문 일이 아닙니다. 심지어 이름도 있습니다. 유창한 인터페이스 라고 합니다 .
Philipp

9
더 읽기 쉬운 결과를 위해 빌더에서이 작성을 추상화 할 수도 있습니다. myCustomDTO = DTOBuilder.defaultDTO().withFoo("foo").withBar("bar").Build();세터가 공허하다는 일반적인 생각과 충돌하지 않기 위해 그렇게 할 것입니다.

9
@Philipp은 기술적으로는 옳지 만 new Foo().setBar('bar').setBaz('baz')매우 유창하다고 느낍니다. 나는 확실히 정확히 같은 방식으로 구현 될 수있는 의미,하지만 난 아주 훨씬 더 같은 것을 읽어 기대Foo().barsThe('bar').withThe('baz').andQuuxes('the quux')
웨인 베르너

답변:


50

이것이 왜 OOP 규칙이 아닌 이유가 있습니까?

내 최선의 추측 : CQS를 위반하기 때문에

명령 (객체 상태 변경)과 쿼리 (이 경우 객체 자체를 반환하는 상태의 복사본을 반환)와 동일한 메소드가 혼합되어 있습니다. 반드시 문제는 아니지만 일부 기본 지침을 위반합니다.

예를 들어 C ++에서 std :: stack :: pop ()은 void를 반환하는 명령이고 std :: stack :: top ()은 스택의 최상위 요소에 대한 참조를 반환하는 쿼리입니다. 고전, 두 결합하고 싶지만, 당신은 할 수 없습니다 예외 안전합니다. (Java의 할당 연산자가 발생하지 않기 때문에 Java에서는 문제가되지 않습니다).

DTO가 가치 유형 인 경우와 비슷한 결과를 얻을 수 있습니다.

public DTO setFoo(String foo) {
    return new DTO(foo, this.bar);
}

public DTO setBar(String bar) {
    return new DTO(this.foo, bar);
}

또한 체인 반환 값은 상속을 처리 할 때 엄청난 고통입니다. "다채롭게 반복되는 템플릿 패턴"을 참조하십시오.

마지막으로, 기본 생성자가 유효한 상태의 객체를 남겨 두어야하는 문제가 있습니다. 개체를 유효한 상태로 복원하기 위해 많은 명령을 실행 해야하는 경우 무언가 잘못되었습니다.


4
마지막으로 여기에 진짜 함정이 있습니다. 상속은 실제 문제입니다. 체인은 구성에 사용되는 데이터 표현 객체에 대한 기존의 세터가 될 수 있다고 생각합니다.
Ben

6
"객체에서 상태의 복사본을 반환"-하지 않습니다.
user253751

2
이 가이드 라인을 따르는 가장 큰 이유는 (반복자와 같은 몇 가지 예외를 제외하고) 코드 가 추론하기가 훨씬 쉽다는 것입니다.
jpmc26

1
@MatthieuM. 아니. 저는 주로 Java를 사용하며 고급 "유체"API에서 널리 사용됩니다. 다른 언어에도 존재합니다. 본질적 으로 유형을 확장하는 유형에서 유형을 일반 으로 만듭니다 . 그런 다음 abstract T getSelf()일반 유형을 반환 하는 메소드 를 추가하십시오 . 이제 반환하는 대신 this, 당신의 세터에서 당신을 return getSelf()하고있는 재정 클래스는 단순히 자신과 수익률의 일반적인 유형 수 this에서을 getSelf. 이런 식으로 setter는 선언 유형이 아닌 실제 유형을 반환합니다.
거미 보리스

1
@MatthieuM. 정말? C ++ 용 CRTP와 비슷한 소리 ...
쓸모없는

33
  1. 몇 번의 키 스트로크 저장은 설득력이 없습니다. 좋을지 모르지만 OOP 규칙은 키 입력이 아닌 개념과 구조에 더 관심이 있습니다.

  2. 반환 값은 의미가 없습니다.

  3. 사용자가 반환 값의 의미를 기대할 수 있기 때문에 의미가없는 것보다 반환 값이 잘못 되었습니다. 그들은 그것이 "불변의 세터"라고 기대할 수있다

    public FooHolder {
        public FooHolder withFoo(int foo) {
            /* return a modified COPY of this FooHolder instance */
        }
    }
    

    실제로 세터는 객체를 변경합니다.

  4. 상속에서는 잘 작동하지 않습니다.

    public FooHolder {
        public FooHolder setFoo(int foo) {
            ...
        }
    }
    
    public BarHolder extends FooHolder {
        public FooHolder setBar(int bar) {
            ...
        }
    } 
    

    난 쓸수있다

    new BarHolder().setBar(2).setFoo(1)

    하지만

    new BarHolder().setFoo(1).setBar(2)

저에게는 # 1에서 # 3까지가 중요한 것들입니다. 잘 작성된 코드는 유쾌하게 배열 된 텍스트에 관한 것이 아닙니다. 잘 작성된 코드는 기본 개념, 관계 및 구조에 관한 것입니다. 텍스트는 코드의 진정한 의미를 외적으로 반영한 것입니다.


2
그러나 이러한 주장의 대부분은 유창하고 체인 가능한 인터페이스에 적용됩니다.
Casey

@Casey, 그렇습니다. 빌더 (즉, 세터 포함)가 체인에서 가장 많이 본 것입니다.
Paul Draper

10
유창한 인터페이스에 대해 잘 알고 있다면 반환 유형에서 유창한 인터페이스를 의심 할 가능성이 있기 때문에 # 3은 적용되지 않습니다. 또한 "잘 작성된 코드는 유쾌하게 배열 된 텍스트가 아닙니다"라는 의견에 동의하지 않아야합니다. 그것이 전부는 아니지만, 잘 배열 된 텍스트는 프로그램의 독자가 적은 노력으로 텍스트를 이해할 수 있도록하기 때문에 다소 중요합니다.
Michael Shaw

1
세터 체인은 텍스트를 즐겁게 배열하거나 키 입력을 저장하는 것이 아닙니다. 식별자의 수를 줄이는 것입니다. setter chaining을 사용하면 단일 표현식으로 객체를 생성하고 설정할 수 있습니다. 즉, 변수에 저장할 필요가 없습니다. 즉 이름을 지정해야하는 변수 가 범위의 끝까지 유지됩니다.
Idan Arye

3
: 점 # 4에 대해서는, 다음과 같이 자바로 해결 될 수있는 일이다 public class Foo<T extends Foo> {...}세터가 복귀와 함께 Foo<T>. 또한 'setter'메소드를 'with'메소드라고 부르는 것이 좋습니다. 과부하가 정상적으로 작동하면 Foo<T> with(Bar b) {...}, 그렇지 않으면 Foo<T> withBar(Bar b).
YoYo

12

나는 이것이 OOP 규칙이라고 생각하지 않으며, 언어 디자인 및 그 규칙과 관련이 있습니다.

Java를 사용하는 것 같습니다. Java에는 setter의 리턴 유형을 무효화하도록 지정 하는 JavaBeans 스펙이 있습니다. 즉 setter의 체인과 충돌합니다. 이 사양은 다양한 도구로 널리 수용되고 구현됩니다.

물론 사양의 일부가 체인이 아닌 이유를 물을 수 있습니다. 나는 대답을 모른다. 아마도이 패턴은 당시에 알려지지 않았거나 인기가 없었습니다.


예, JavaBeans는 내가 염두에 두었던 것입니다.
Ben

JavaBeans를 사용하는 대부분의 응용 프로그램은 생각하지 않지만 리플렉션을 사용하여 'set ...'이라는 이름의 메서드를 가져 오고 단일 매개 변수를 가지며 void return 인 메서드를 사용할 수 있습니다. 많은 정적 분석 프로그램은 무언가를 반환하는 메소드에서 반환 값을 확인하지 않는 것에 대해 불평하며 이는 매우 성 가실 수 있습니다.

Java에서 메소드를 가져 오는 @MichaelT 반사 호출은 리턴 유형을 지정하지 않습니다. 그런 식으로 return 메소드를 호출하면 메소드 가 리턴 Object유형 Void으로 지정된 경우 싱글 톤 유형 이 리턴 될 수 있습니다 void. 다른 소식에서, "의견을 남기고 투표하지 않는 것"은 "첫 번째 게시물"감사가 실패하더라도 좋은 의견 일 수 있습니다. 나는 이것을 shog를 비난한다.

7

다른 사람들이 말했듯이, 이것은 종종 유창한 인터페이스 라고합니다 .

일반적으로 setter는 응용 프로그램 의 논리 코드에 대한 응답으로 변수 를 전달하는 호출입니다 . DTO 수업이 이에 대한 예입니다. setter가 아무것도 반환하지 않을 때의 기존 코드는 정상적입니다. 다른 답변은 방법을 설명했습니다.

그러나 유창한 인터페이스가 좋은 솔루션 일 수 있는 몇 가지 경우가 있습니다.

  • 상수는 주로 세터에게 전달됩니다.
  • 프로그램 로직은 세터에 전달되는 내용을 변경하지 않습니다.

fluent-nhibernate 와 같은 구성 설정

Id(x => x.Id);
Map(x => x.Name)
   .Length(16)
   .Not.Nullable();
HasMany(x => x.Staff)
   .Inverse()
   .Cascade.All();
HasManyToMany(x => x.Products)
   .Cascade.All()
   .Table("StoreProduct");

특수 TestDataBulderClasses (객체 어머니)를 사용하여 단위 테스트에서 테스트 데이터 설정

members = MemberBuilder.CreateList(4)
    .TheFirst(1).With(b => b.WithFirstName("Rob"))
    .TheNext(2).With(b => b.WithFirstName("Poya"))
    .TheNext(1).With(b => b.WithFirstName("Matt"))
    .BuildList(); // Note the "build" method sets everything else to
                  // senible default values so a test only need to define 
                  // what it care about, even if for example a member 
                  // MUST have MembershipId  set

그러나 유창한 인터페이스를 만드는 것은 매우 어렵 기 때문에 "정적"설정이 많은 경우에만 유용합니다. 또한 유창한 인터페이스는 "정상"클래스와 혼용해서는 안됩니다. 따라서 빌더 패턴이 자주 사용됩니다.


5

나는 하나의 세터를 다른 체인으로 연결하는 것이 관례가 아닌 많은 이유를 생각합니다.이 경우 생성자에서 옵션 객체 또는 매개 변수를 보는 것이 더 일반적이기 때문입니다. C #에는 초기화 구문도 있습니다.

대신에:

DTO dto = new DTO().setFoo("foo").setBar("bar");

하나는 쓸 수 있습니다 :

(JS에서)

var dto = new DTO({foo: "foo", bar: "bar"});

(C #에서)

DTO dto = new DTO{Foo = "foo", Bar = "bar"};

(자바)

DTO dto = new DTO("foo", "bar");

setFoo그리고 setBar다음 더 이상 초기화 필요하지 않습니다, 나중에 돌연변이에 사용할 수 있습니다.

체인 가능성은 일부 상황에서 유용하지만 줄 바꿈 문자를 줄이기 위해 모든 것을 한 줄로 채우지 않는 것이 중요합니다.

예를 들어

dto.setFoo("foo").setBar("fizz").setFizz("bar").setBuzz("buzz");

무슨 일이 일어나고 있는지 이해하고 이해하기가 어렵습니다. 다음으로 다시 포맷 :

dto.setFoo("foo")
    .setBar("fizz")
    .setFizz("bar")
    .setBuzz("buzz");

이해하기가 훨씬 쉽고 첫 번째 버전의 "실수"를보다 분명하게 만듭니다. 코드를 해당 형식으로 리팩토링하면 다음과 같은 이점이 없습니다.

dto.setFoo("foo");
dto.setBar("bar");
dto.setFizz("fizz");
dto.setBuzz("buzz");

3
1. 가독성 문제에 동의 할 수 없습니다. 각 호출이 더 명확 해지기 전에 인스턴스를 추가하십시오. 2. js와 C #으로 보여준 초기화 패턴은 생성자와 아무 관련이 없습니다 : js에서 한 일은 단일 인수로 전달되며 C #에서 한 것은이면 뒤에 geters와 setter를 호출하는 구문 shugar입니다. C #처럼 geter-setter shugar가 없습니다.
Ben

1
@Benedictus, OOP 언어가 체인 없이이 문제를 처리하는 다양한 방법을 보여주었습니다. 요점은 동일한 코드를 제공하는 것이 아니라 연쇄를 불필요하게 만드는 대안을 제시하는 것이 었습니다.
zzzzBov

2
"각 호출 전에 인스턴스를 추가해도 전혀 명확하지는 않습니다."나는 각 호출 전에 인스턴스를 추가하면 더 명확 해 졌다고 주장한 적이 없으며, 상대적으로 동등한 것이라고 말했습니다.
zzzzBov

1
VB.NET에는 또한 사용 가능한 "With"키워드가있어 컴파일러 임시 참조를 생성하므로 예를 들어 [ /줄 바꿈을 나타내는 데 사용 ] With Foo(1234) / .x = 23 / .y = 47은와 같습니다 Dim temp=Foo(1234) / temp.x = 23 / temp.y = 47. 이후 이러한 구문은 모호성을 생성하지 않는 .x바로 문 "과 함께"주변 [없음, 또는 어떤 멤버가없는 개체가없는 경우에 바인딩하는 것보다 더 의미 다른를 가질 수 그 자체로 x, 다음 .x입니다 의미]. 오라클은 Java와 같은 것을 포함하지 않았지만 그러한 구성은 언어에 매끄럽게 맞습니다.
supercat

5

이 기술은 실제로 빌더 패턴에서 사용됩니다.

x = ObjectBuilder()
        .foo(5)
        .bar(6);

그러나 일반적으로 모호하기 때문에 피해야합니다. 반환 값이 객체인지 (따라서 다른 설정자를 호출 할 수 있는지) 또는 반환 객체가 방금 할당 된 값인지 (공통 패턴 임) 확실하지 않습니다. 따라서 최소한의 서프라이즈 원칙은 객체 디자인의 기본이 아니라면 사용자가 하나의 솔루션 또는 다른 솔루션을 보길 원한다고 가정해서는 안된다고 제안합니다.


6
차이점은 빌더 패턴을 사용할 때 일반적으로 읽기 전용 (불변) 오브젝트 (빌드하는 클래스에 상관없이)를 구성하는 쓰기 전용 오브젝트 (빌더)를 작성한다는 것입니다. 이와 관련하여, 단일 호출 로 사용될 수 있기 때문에 긴 일련의 메소드 호출을 갖는 것이 바람직하다 .
Darkhogg

"또는 반환 객체가 방금 할당 된 값이면"방금 전달한 값을 유지하려면 변수에 먼저 넣습니다. 그래도 이전에 할당 된 값을 얻는 것이 유용 할 수 있습니다 (일부 컨테이너 인터페이스가이를 수행한다고 생각합니다).
JAB February

@JAB 동의합니다. 나는 그 표기법의 팬이 아니지만 그 자리가 있습니다. 마음에 오는 하나는 Obj* x = doSomethingToObjAndReturnIt(new Obj(1, 2, 3)); 나는 그것이 또한 때문에 거울 인기를 얻고 생각 a = b = c = d나는 인기는 잘 설립 확신 아니에요 불구하고. 일부 원자 연산 라이브러리에서 이전 값을 반환하는 것을 언급했습니다. 이야기의 교훈? 내가 소리를내는 것보다 더 혼란 스럽다 =)
Cort Ammon

나는 학자들이 약간의 비판적이라고 생각합니다. 관용구에는 본질적으로 아무런 문제가 없습니다. 어떤 경우에는 명확성을 추가하는 데 매우 유용합니다. 예를 들어 문자열의 모든 줄을 들여 쓰고 그 주위에 구식 상자를 그리는 다음과 같은 텍스트 서식 클래스를 사용하십시오 new BetterText(string).indent(4).box().print();. 이 경우, 나는 gobbledygook를 우회하고 문자열을 가져 와서 들여 넣고 상자에 넣고 출력했습니다. 캐스케이드 (예 :)와 같은 복제 방법 .copy()을 사용하여 모든 후속 작업에서 더 이상 원본을 수정할 수 없도록 할 수도 있습니다.
tgm1024

2

이것은 답변보다 더 많은 의견이지만 의견을 말할 수는 없습니다.

나는이 질문이 전혀 드문 것으로 보지 않기 때문에이 질문이 나를 놀라게했다고 언급하고 싶었습니다 . 사실, 제 작업 환경 (웹 개발자)은 매우 일반적입니다.

생성 : 예를 들어,이 심포니의 교리는 어떻게 개체가 자동 생성 명령 을 모두 setters , 기본적으로 .

jQuery를이 매우 유사한 방식으로 체인 메서드의 대부분을.


Js는 혐오입니다
Ben

@ 베네딕토 스 나는 PHP가 더 큰 가증이라고 말합니다. JavaScript는 완벽하게 훌륭한 언어이며 ES6 기능을 포함하여 꽤 좋았습니다 (일부 사람들은 여전히 ​​CoffeeScript 또는 변형을 선호하지만 개인적으로는 CoffeeScript가 변수 범위를 처리하는 방법에 대한 팬이 아닙니다. 파이썬이하는 방식이 아닌 로컬 / 글로벌로 명시 적으로 표시되지 않는 한 로컬 범위에 할당 된 변수를 로컬로 처리하는 것보다 먼저).
JAB February

@Benedictus와 동의합니다 JAB. 나는 대학 시절에 JS를 가증스러운 스크립트 언어로 보았지만 몇 년 동안 일하고 올바른 사용법을 배우고 나면 실제로 사랑 합니다. . 그리고 ES6에서는 정말 성숙한 언어 가 될 것이라고 생각합니다 . 그러나 내 머리를 돌리게 만드는 것은 ... 내 대답은 JS와 어떤 관련이 있는가? ^^ U
xDaizu

나는 실제로 js와 2 년을 일했으며 그것이 방법을 배웠다고 말할 수 있습니다. 그럼에도 불구하고 나는이 언어를 단지 실수 일 뿐이라고 본다. 하지만 동의합니다. Php가 훨씬 더 나쁩니다.
Ben

@ 베네딕토 스 나는 당신의 입장을 이해하고 그것을 존중합니다. 그래도 여전히 마음에 듭니다. 물론, 그것은 특질과 특권을 가지고 있지만 (언어는 그렇지 않습니까?) 꾸준히 올바른 방향으로 나아가고 있습니다. 하지만 .. 난 실제로 그것을 너무 좋아해서 방어해야합니다. 나는 그것을 사랑하거나 미워하는 사람들로부터 아무것도 얻지 못합니다 ... hahaha 또한, 여기 에는 그런 곳이 없습니다. JS에 대한 대답조차 전혀 없었습니다.
xDaizu
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.