메소드 체인-왜 좋은 습관입니까?


151

메소드 체인 은 다른 메소드에 대해 결과를 호출하기 위해 오브젝트 자체를 리턴하는 오브젝트 메소드의 실습입니다. 이처럼 :

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

읽을 수있는 코드 또는 "유창한 인터페이스"를 생성하기 때문에 이는 좋은 습관으로 간주됩니다. 그러나 나에게 대신 객체 지향 자체에 의해 암시 된 표기법을 호출하는 것으로 보입니다. 결과 코드는 이전 방법 의 결과 에 대한 작업 수행을 나타내지 않습니다 . 이는 객체 지향 코드가 일반적으로 작동하는 방식입니다.

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

이 차이는 "결과 객체 호출"의 점 표기법에 대해 두 가지 의미를 생성합니다. 연결의 맥락에서, 위 의 예는 실제로 일정을 저장하려는 의도에도 불구하고 참가자 객체 를 저장하는 것으로 읽습니다. getSchedule에 의해 수신 된 오브젝트.

여기서의 차이점은 호출 된 메소드가 무언가를 반환 해야하는지 여부입니다 (이 경우 체인을 위해 호출 된 객체 자체를 반환합니다). 그러나이 두 경우는 표기법 자체와 구별 할 수 없으며 호출되는 메소드의 의미론과 만 구별됩니다. 메소드 체인을 사용하지 않으면 메소드 호출 이 이전 호출 의 결과 와 관련된 것으로 작동한다는 것을 항상 알 수 있습니다. 체인을 사용하면이 가정이 깨지고 실제 객체가 무엇인지 이해하기 위해 전체 체인을 의미 론적으로 처리해야합니다. 정말이라고합니다. 예를 들면 다음과 같습니다.

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

마지막 두 메소드 호출은 getSocialStream의 결과를 참조하지만 이전의 메소드 호출은 참여자를 참조합니다. 어쩌면 컨텍스트가 변경되는 체인을 실제로 작성하는 것은 좋지 않습니다 (그렇지 않습니까?). 그렇더라도 비슷한 모양의 도트 체인이 실제로 동일한 컨텍스트 내에 있는지 또는 결과에 대해서만 작동하는지 지속적으로 확인해야합니다 .

나에게 메소드 체인이 피상적으로 읽을 수있는 코드를 생성하는 동안 점 표기법의 의미를 오버로드하면 혼란이 더 커질 것 같습니다. 내가 프로그래밍 전문가라고 생각하지 않기 때문에 나는 내 잘못이라고 생각한다. 그래서 : 나는 무엇을 놓치고 있습니까? 어떻게 든 메소드 체인을 잘못 이해합니까? 메소드 체인이 특히 좋은 경우 또는 특히 나쁜 경우가 있습니까?

주석 :이 질문은 질문으로 가려진 의견 진술로 읽을 수 있음을 이해합니다. 그러나 체인이 좋은 습관으로 여겨지는 이유를 이해하고 고유 한 객체 지향 표기법을 위반한다고 생각하면 어디에서 잘못되었는지 이해하고 싶습니다.


메소드 체인은 Java로 스마트 코드를 작성하는 최소한 한 가지 방법 인 것 같습니다. 모두가 동의하지 않더라도 ..
Mercer Traieste

또한 이러한 "유창한"메소드 또는 "유창한"인터페이스라고도합니다. 이 용어를 사용하도록 제목을 업데이트 할 수 있습니다.
S.Lott

4
또 다른 SO 토론에서 유창한 인터페이스는 코드 가독성과 관련이있는 더 큰 개념이며 메서드 체인은이를 목표로하는 한 가지 방법 일뿐입니다. 그들은 밀접하게 관련되어 있으므로 태그를 추가하고 텍스트에 유창한 인터페이스를 참조했습니다. 이것으로 충분하다고 생각합니다.
Ilari Kajaste

14
내가 이것을 생각하게 된 방법은 실제로 메서드 체인은 언어 구문에 누락 된 기능을 얻는 해킹입니다. 내장 된 대체 표기법이 .있으면 mehtod 반환 값을 무시하고 항상 동일한 객체를 사용하여 연결된 메서드를 호출하는 것이 실제로 필요하지 않습니다.
Ilari Kajaste

훌륭한 코딩 관용구이지만 다른 모든 훌륭한 도구와 마찬가지로 악용됩니다.
Martin Spamer

답변:


74

나는 이것이 주관적인 것에 동의합니다. 대부분의 경우 메소드 체인을 피하지만 최근에는 그것이 옳은 경우를 발견했습니다 .10 개의 매개 변수와 같은 것을 받아들이고 더 많은 것을 필요로하는 메소드가 있었지만 대부분의 경우 조금. 재정의로 인해 이것은 매우 번거로 웠습니다. 대신 체인 방식을 선택했습니다.

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

메소드 체인 접근 방식은 선택 사항이지만 코드 작성이 더 쉬워졌습니다 (특히 IntelliSense 사용). 이것은 하나의 고립 된 사례이며 내 코드의 일반적인 관행이 아님을 명심하십시오.

요점은-99 %의 경우 메소드 체인을 사용하지 않고도 아마도 더 잘 할 수 있습니다. 그러나 이것이 가장 좋은 방법 인 1 %가 있습니다.


4
이 시나리오에서 메소드 체인을 사용하는 가장 좋은 방법은 IMO와 같이 함수에 전달할 매개 변수 오브젝트를 작성하는 것 P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);입니다. 다른 호출에서이 매개 변수 객체를 재사용 할 수 있다는 장점이 있습니다.
pedromanoel 2016 년

20
패턴에 대한 저의 공헌도, 팩토리 메소드에는 일반적으로 하나의 작성 지점 만 있으며 제품은 팩토리 메소드의 매개 변수를 기반으로하는 정적 선택입니다. 체인 작성 외모보다 당신이 결과를 얻기 위해 다른 방법을 부르는 빌더 패턴, 방법을 같이 선택이 될 수 방법 체인 , 우리는 같은 수 PizzaBuilder.AddSauce().AddDough().AddTopping()의 참조를 여기
마르코 메드 라노

3
메소드 체인 (원래 질문에서 chown으로)은 demeter의 법칙을 위반할 때 나쁜 것으로 간주됩니다 . 참조 : ifacethoughts.net/2006/03/07/… 여기에 제시된 답변은 실제로 "빌더 패턴"이므로 해당 법을 따릅니다.
Angel O'Sphere

2
@Marco Medrano PizzaBuilder 예제는 자바 월드에서 읽은 이래로 항상 버그를 일으켰습니다. 요리사가 아닌 피자에 소스를 추가해야한다고 생각합니다.
Breandán Dalton 2012

1
무슨 말인지 알아요 내가 읽은 때 아직 list.add(someItem), 나는 그대로 "이 코드가 추가되어 읽을 someItem받는 list객체". 그래서을 읽을 때 PizzaBuilder.AddSauce()"이 코드는 소스를 소스에 추가하고 PizzaBuilder있습니다." 다시 말해, 코드 list.add(someItem)또는 PizzaBuilder.addSauce()내장 방법으로 오케 스트레이터 (추가하는 사람)를 봅니다. 그러나 나는 PizzaBuilder 예제가 약간 인공적이라고 생각합니다. 당신의 모범은 MyObject.SpecifySomeParameter(asdasd)저에게 잘 작동합니다.
Breandán Dalton

78

단지 내 2 센트;

메소드 체인은 디버깅을 까다롭게 만듭니다.-중단 점을 간결한 지점에 둘 수 없으므로 원하는 위치에서 프로그램을 정확하게 일시 중지 할 수 있습니다.-이러한 메소드 중 하나에서 예외가 발생하고 행 번호를 얻는다면 전혀 알 수 없습니다. "체인"의 어떤 방법으로 문제가 발생했는지

나는 항상 매우 짧고 간결한 줄을 쓰는 것이 일반적으로 좋은 습관이라고 생각합니다. 모든 라인은 하나의 메소드 호출 만해야합니다. 더 긴 줄보다 더 많은 줄을 선호하십시오.

편집 : 의견은 메소드 체인과 줄 바꿈이 분리되어 있다고 언급합니다. 사실입니다. 디버거에 따라 명령문 중간에 중단 점을 배치 할 수도 있고 불가능할 수도 있습니다. 가능한 경우에도 중간 변수가있는 별도의 줄을 사용하면 조사 프로세스에서 디버깅 프로세스를 도와주는 훨씬 많은 유연성과 조사 할 수있는 많은 값을 얻을 수 있습니다.


4
줄 바꿈과 메소드 체인을 독립적으로 사용하지 않습니까? @ Vilx- 답변에 따라 각 호출을 줄 바꿈으로 연결할 수 있으며 일반적으로 동일한 줄에 여러 개의 개별 명령문을 넣을 수 있습니다 (예 : Java의 세미콜론 사용).
brabster 2018 년

2
이것은 대답이 완전히 정당화되었지만, 내가 아는 모든 디버거에 존재하는 약점을 나타내며 질문과 관련이 없습니다.
masterxilo

1
@ 브 래스터. 그것들은 특정 디버거마다 분리되어있을 수 있지만, 중간 변수로 별도의 호출을하면 버그를 조사하는 동안 훨씬 더 많은 정보를 얻을 수 있습니다.
RAY

4
+1, 어떤 시점에서 메소드 체인을 단계 디버깅 할 때 각 메소드가 무엇을 반환했는지 알고 있습니다. 메소드 체인 패턴은 해킹입니다. 유지 보수 프로그래머의 최악의 악몽입니다.
Lee Kowalkowski

1
나는 체인을 좋아하는 팬이 아니지만 메소드 정의에 중단 점을 두지 않는 이유는 무엇입니까?
Pankaj Sharma

39

개인적으로 저는 여러 속성 설정 또는 유틸리티 유형 메서드 호출과 같이 원본 객체에서만 작동하는 체인 메서드를 선호합니다.

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

하나 이상의 체인 메소드가 내 예제에서 foo 이외의 객체를 반환 할 때 사용하지 않습니다. 체인에서 해당 객체에 대해 올바른 API를 사용하는 한 구문 상으로 체인을 연결할 수 있지만, 객체를 변경하면 IMHO가 덜 읽기 쉽고 다른 객체의 API에 유사성이있는 경우 실제로 혼란을 줄 수 있습니다. 당신이 마지막에 정말 일반적인 메서드 호출을 할 경우 ( .toString(), .print(), 무엇이든) 어떤 객체는 결국에 작용하는거야? 코드를 우연히 읽는 사람은 원래 참조가 아니라 체인에서 암시 적으로 반환되는 객체라는 것을 알지 못할 수 있습니다.

다른 객체를 연결하면 예기치 않은 null 오류가 발생할 수 있습니다. 내 예제에서 foo 가 유효 하다고 가정하면 모든 메소드 호출은 "안전"합니다 (예 : foo에 유효). OP의 예에서 :

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

... getSchedule이 실제로 null이 아닌 유효한 일정 객체를 반환한다는 보장은 없습니다 (코드를보고있는 외부 개발자로서). 또한 많은 IDE가 디버그 할 때 메소드 호출을 검사 할 수있는 객체로 평가하지 않기 때문에이 스타일의 코드를 디버깅하는 것이 훨씬 어렵습니다. IMO, 디버깅 목적으로 검사하기 위해 객체가 필요할 때마다 명시 적 변수로 사용하는 것이 좋습니다.


A는 그 기회가 있으면 Participant유효하지 않습니다 Schedule다음 getSchedule메소드가 반환하도록 설계 Maybe(of Schedule)유형과 saveTo방법이 수용하도록 설계 Maybe유형.
Lightman

24

Martin Fowler는 여기서 좋은 토론을합니다.

메소드 체인

사용시기

Method Chaining은 내부 DSL의 가독성을 크게 향상시켜 결과적으로 일부 DSL의 내부 동의어가되었습니다. 그러나 메소드 체인은 다른 기능 조합과 함께 사용할 때 가장 좋습니다.

메소드 체인은 특히 parent :: = (this | that) *와 같은 문법에 효과적입니다. 다른 방법을 사용하면 다음에 어떤 인수가 올지 알 수있는 읽기 쉬운 방법을 제공합니다. 마찬가지로 메소드 인수를 사용하여 선택적 인수를 쉽게 건너 뛸 수 있습니다. parent :: = first second와 같은 필수 절 목록은 기본 인터페이스에서는 잘 작동하지 않지만 점진적 인터페이스를 사용하면 제대로 지원 될 수 있습니다. 대부분의 경우 그 경우 중첩 기능을 선호합니다.

Method Chaining의 가장 큰 문제는 마무리 문제입니다. 해결 방법이 있지만 일반적 으로이 문제가 발생하면 중첩 함수를 사용하는 것이 좋습니다. 컨텍스트 변수로 혼란에 빠지면 중첩 함수를 사용하는 것이 좋습니다.


2
링크가 죽었습니다 :(
Qw3ry

DSL의 의미는 무엇입니까? 도메인 특정 언어
Sören

@ Sören : Fowler는 도메인 별 언어를 나타냅니다.
Dirk Vollmar

21

내 의견으로는, 메소드 체인은 약간의 참신함입니다. 물론, 그것은 시원해 보이지만 실제 이점은 보이지 않습니다.

어때:

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

보다 나은 것 :

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

addObject ()가 새 객체를 반환하는 경우는 예외입니다.이 경우 연결되지 않은 코드는 다음과 같이 조금 더 성 가실 수 있습니다.

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

9
첫 번째 예제에서도 'someList'부분 중 두 개를 피하고 세 개가 아닌 한 줄로 끝나기 때문에 더 간결합니다. 그것이 실제로 좋은지 나쁜지는 다른 것들에 달려 있고 아마도 맛의 문제 일 것입니다.
Fabian Steeg

26
'someList'를 한 번만 사용하면 얻을 수있는 장점은 더 길고 설명적인 이름을 지정하는 것이 훨씬 쉽다는 것입니다. 이름을 빠르게 연속해서 여러 번 표시해야 할 때마다 이름을 짧게 (반복을 줄이고 가독성을 높이기 위해) 만드는 경향이있어 설명이 덜되어 가독성이 떨어집니다.
Chris Dodd

메소드 체인화는 주어진 메소드의 리턴 값의 내부 검사를 방지하고 /하거나 체인의 모든 메소드에서 빈 목록 / 세트 및 널이 아닌 값의 추정을 암시한다는 점에서 가독성과 DRY 원칙에 대한 좋은 지적 시스템에서 많은 NPE를 자주 디버깅 / 수정해야합니다).
Darrell Teague

@ChrisDodd 자동 완성에 대해 들어 본 적이 없습니까?
inf3rno

1
"인간 뇌는 동일한 들여 쓰기가있는 경우 텍스트 반복을 인식하는 데 매우 능숙합니다."그것은 반복의 문제입니다. 뇌가 반복되는 패턴에 집중하게합니다. 따라서 코드를 읽고 이해하려면 뇌가 반복되는 패턴을 넘어서 / 뒤에 서서 실제로 진행되고있는 것을 확인해야합니다. 뇌가 광택을내는 경향이 반복되는 패턴의 차이점입니다. 이것이 반복성이 코드 가독성에 좋지 않은 이유입니다.
Chris Dodd

7

예상보다 많은 객체에 의존 할 수 있으므로 위험하므로 다른 클래스의 인스턴스를 반환합니다.

예를 들어 보겠습니다.

foodStore는 소유 한 많은 식품점으로 구성된 개체입니다. foodstore.getLocalStore ()는 가장 가까운 상점에 대한 정보를 보유한 객체를 매개 변수에 반환합니다. getPriceforProduct (anything)는 해당 객체의 메소드입니다.

따라서 foodStore.getLocalStore (parameters) .getPriceforProduct (anything)를 호출하면

FoodStore뿐만 아니라 LocalStore에도 의존하고 있습니다.

getPriceforProduct (anything)가 변경되면 FoodStore뿐만 아니라 chained 메소드를 호출 한 클래스도 변경해야합니다.

클래스 간 느슨한 결합을 항상 목표로해야합니다.

즉, 루비를 프로그래밍 할 때 개인적으로 연결하는 것을 좋아합니다.


7

많은 사람들이 가독성 문제를 염두에 두지 않고 메서드 체인을 편의의 형태로 사용합니다. 메소드 체인은 동일한 오브젝트에서 동일한 조치를 수행하는 경우 허용되지만 코드 작성이 적을뿐 아니라 실제로 가독성을 향상시키는 경우에만 허용됩니다.

불행히도 많은 사람들이 질문에 주어진 예에 따라 메소드 체인을 사용합니다. 그들이하는 동안 수 있습니다 여전히 읽을 할, 그들은 불행하게도 여러 클래스 사이의 높은 커플 링을 일으키는, 그래서 그것은 바람직하지 않습니다.


6

이것은 다소 주관적인 것 같습니다.

메쏘드 체이닝은 본질적으로 나쁘거나 좋은 imo 인 것이 아닙니다.

가독성이 가장 중요합니다.

(또한 많은 수의 메서드를 체인으로 연결하면 무언가가 바뀌면 매우 취약합니다)


실제로 주관적 일 수 있으므로 주관적 태그입니다. 대답이 나에게 강조되기를 희망하는 경우는 메소드 체인이 좋은 아이디어가 될 것입니다. 지금은 많이 보지 못하지만 이것이 개념의 장점을 이해하는 것이 아니라고 생각합니다. 체인 자체에 본질적으로 나쁜 것.
Ilari Kajaste

커플 링이 높으면 본질적으로 나쁘지 않습니까? 체인을 개별 문장으로 나누더라도 가독성이 떨어지지 않습니다.
aberrant80

1
당신이 얼마나 독단적으로되고 있는지에 달려 있습니다. 더 읽기 쉬운 결과가 나오면 많은 상황에서 선호 될 수 있습니다. 이 접근법의 가장 큰 문제는 객체의 대부분의 메소드가 객체 자체에 대한 참조를 반환하지만 종종 메소드는 더 많은 메소드를 연결할 수있는 하위 객체에 대한 참조를 반환한다는 것입니다. 이 작업을 시작하면 다른 코더가 진행중인 작업을 해체하기가 매우 어려워집니다. 또한 메소드의 기능이 변경되면 큰 복합 명령문에서 디버그하기가 어려울 것입니다.
John Nicholas

6

Chaining의 장점,
즉 내가 사용하는 곳

내가 언급하지 않은 체인의 이점 중 하나는 변수를 시작하는 동안 또는 새 객체를 메서드에 전달할 때 사용하는 기능인데, 이것이 나쁜 습관인지 아닌지 확실하지 않습니다.

나는 이것이 고안된 예라는 것을 알고 있지만 다음과 같은 수업이 있다고 말합니다.

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

기본 클래스에 액세스 할 수 없거나 시간 등을 기준으로 기본값이 동적이라고 말합니다. 예를 들어 인스턴스화 한 다음 값을 변경할 수 있지만 특히 지나가는 경우 번거로울 수 있습니다 메소드의 값 :

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

그러나 이것은 읽기가 훨씬 쉽지는 않습니다.

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

아니면 반원은 어떻습니까?

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

vs

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

이것은 체인을 사용하는 방법이며 일반적으로 내 방법은 구성을위한 것이므로 길이가 2 줄에 불과한 다음 값을 설정하십시오 Return Me. 우리에게는 코드처럼 읽고 이해하기 어려운 큰 줄을 문장처럼 읽는 한 줄로 정리했습니다. 같은

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

Vs 같은 것

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)

체인을 손상시키는 것
, 즉 사용하고 싶지 않은 곳

루틴에 전달할 매개 변수가 많을 때 체인을 사용하지 않습니다. 주로 줄이 매우 길어지기 때문에 OP가 언급했듯이 루틴을 다른 클래스에 호출하여 루틴 중 하나에 전달하면 혼동 될 수 있습니다 연결 방법.

루틴이 유효하지 않은 데이터를 리턴 할 것이라는 우려도 있습니다. 지금까지 호출 된 동일한 인스턴스를 리턴 할 때만 체인을 사용했습니다. 클래스간에 체인을 연결하면 디버깅이 더 어려워지고 (어느 것이 null을 반환합니까?) 클래스 간의 종속성 커플 링을 증가시킬 수 있습니다.

결론

인생의 모든 것, 프로그래밍과 마찬가지로 체인도 좋지 않습니다. 나쁜 것을 피할 수 있다면 체인이 큰 이점이 될 수 있습니다.

이 규칙을 따르려고합니다.

  1. 클래스 사이를 연결하지 마십시오
  2. 체인을 위해 특별히 루틴 만들기
  3. 연결 루틴에서 한 가지만 수행
  4. 가독성을 향상시킬 때 사용하십시오
  5. 코드를 간단하게 만들 때 사용하십시오.

6

메소드 체인을 사용하면 Java로 고급 DSL 을 직접 설계 할 수 있습니다. 본질적으로 다음 유형의 DSL 규칙을 모델링 할 수 있습니다.

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

이러한 규칙은 이러한 인터페이스를 사용하여 구현할 수 있습니다

// 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();
}

이러한 간단한 규칙을 사용하면 내가 만든 라이브러리 인 jOOQ 에 의해 수행되는 것과 같이 SQL과 같은 복잡한 DSL을 Java로 직접 구현할 수 있습니다 . 내 블로그 에서 가져온 다소 복잡한 SQL 예제를 여기에서보십시오.

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()

또 다른 좋은 예입니다 jRTF , 자바에서 직접 RTF 문서를 cerating을 위해 설계된 작은 DSL. 예를 들면 :

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 : 예, 인터페이스 및 하위 유형 다형성과 같은 것을 알고있는 거의 모든 객체 지향 프로그래밍 언어에서 사용할 수 있습니다
Lukas Eder

4

메쏘드 체이닝은 대부분의 경우에 참신한 것이지만, 그것이 적절하다고 생각합니다. CodeIgniter의 Active Record 사용 에서 한 가지 예를 찾을 수 있습니다 .

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

그것은 다음보다 훨씬 깨끗해 보입니다 (그리고 내 의견으로는 더 의미가 있습니다).

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

정말 주관적입니다. 모두 자신의 의견이 있습니다.


이것은 유창함 인터페이스의 예 유스 케이스는 여기에 약간 다릅니다 그래서 방법 체인. 당신은 체인을 연결하는 것이 아니라 쉽게 읽을 수있는 내부 도메인 특정 언어를 만들고 있습니다. 참고로 CI의 ActiveRecord는 ActiveRecord가 아닙니다.
Gordon

3

제 1의 오해는 실제로 이것이 객체 지향적 접근 방식이라고 생각하는 것입니다. 사실 그것이 다른 것보다 기능적 프로그래밍 접근 방식에 더 가깝습니다.

내가 사용하는 주된 이유는 가독성과 변수로 코드가 침수되는 것을 방지하기 위해서입니다.

나는 다른 사람들이 가독성을 손상 시킨다고 말할 때 무슨 말을하는지 이해하지 못합니다. 내가 사용한 가장 간결하고 응집력있는 프로그래밍 형식 중 하나입니다.

또한 이것 :

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

내가 일반적으로 사용하는 방법입니다. x 개의 매개 변수를 연결하는 데 사용하는 것이 일반적으로 사용되는 방식이 아닙니다. 메소드 호출에 x 개의 매개 변수를 넣고 싶다면 params 구문을 사용 합니다.

공공 무효 foo (params object [] items)

유형에 따라 객체를 캐스트하거나 사용 사례에 따라 데이터 유형 배열 또는 컬렉션을 사용하십시오.


1
"1 차 오해에 +1은 이것이 실제로 객체 지향적 접근 이라고 생각하는 것입니다. 다른 무엇보다 기능적인 프로그래밍 접근법 것이다". 눈에 띄는 유스 케이스는 객체에 대한 상태가없는 조작을위한 것입니다 (상태를 변경하는 대신 계속 작동하는 새 객체를 반환하는 경우). OP의 질문과 다른 답변은 사실적으로 연결이 어려워 보이는 상태 저장 작업을 보여줍니다.
OmerB

예, 일반적으로 새 객체를 만들지 않지만 대신 의존성 주입을 사용하여 사용 가능한 서비스로 만드는 것을 제외하고는 상태가없는 조작입니다. 그리고 예 상태 저장 유스 케이스는 메소드 체인을 의도 한 것이 아닙니다. 내가 볼 수있는 유일한 예외는 일부 설정으로 DI 서비스를 초기화하고 어떤 종류의 COM 서비스와 같은 상태를 모니터링하는 일종의 감시 장치가있는 경우입니다. 그냥 IMHO.
Shane Thorndike

2

본인은 이에 따라 라이브러리에서 유창한 인터페이스가 구현되는 방식을 변경했습니다.

전에:

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

후:

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

"이전"구현에서 함수는 객체를 수정하고로 끝났습니다 return this. 동일한 유형의 새 객체반환 하도록 구현을 변경했습니다 .

이 변경에 대한 나의 추론 :

  1. 반환 값은 함수와 아무 관련이 없었으며 순전히 연결 부분을 지원하기 위해 존재했습니다. OOP에 따라 void 함수였습니다.

  2. 시스템 라이브러리의 메소드 체인은 linq 또는 문자열과 같은 방식으로 구현합니다.

    myText = myText.trim().toUpperCase();
    
  3. 원래 객체는 그대로 유지되므로 API 사용자가 처리 할 작업을 결정할 수 있습니다. 다음을 허용합니다.

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. 복사 구현은 객체를 구축하기위한도 사용할 수 있습니다 :

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

    를 Where setBackground(color)기능은 예를 반환 아무것도 변경되지 않습니다 (자사에 가정처럼) .

  5. 기능의 동작이보다 예측 가능합니다 (1 및 2 지점 참조).

  6. 짧은 변수 이름을 사용하면 모델에 API를 강요하지 않고도 코드 혼잡을 줄일 수 있습니다.

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

결론 :
제 생각에는 return this구현 을 사용하는 유창한 인터페이스가 잘못되었습니다.


1
그러나 특히 큰 항목을 사용하는 경우 각 호출에 대해 새 인스턴스를 반환하지 않으면 약간의 오버 헤드가 발생하지 않습니까? 적어도 관리되지 않는 언어의 경우.
Apeiron

@Apeiron 성능 측면에서 보면 return this관리 또는 기타 방식이 훨씬 빠릅니다 . 그것이 "자연스럽지 않은"방식으로 유창한 API를 얻는다고 생각합니다. (이유 6 : 추가 된 유창하지 않은 대안을 보여주기 위해, 오버 헤드 / 추가 기능이없는)
Bob Fanger

동의합니다. DSL의 기본 상태를 그대로두고 모든 메소드 호출에서 새 객체를 반환하는 것이 가장 좋습니다. 대신 궁금합니다. 언급 한 라이브러리가 무엇입니까?
Lukas Eder

1

여기서 완전히 놓친 점은 메소드 체인이 DRY를 허용한다는 것 입니다. "with"(일부 언어에서는 제대로 구현되지 않음)의 효과적인 스탠드 인입니다.

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

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

이것은 DRY가 항상 중요한 것과 같은 이유로 중요합니다. A가 오류로 판명되고 이러한 작업을 B에서 수행해야하는 경우 3이 아닌 한 곳에서만 업데이트하면됩니다.

실제로이 경우 이점이 적습니다. 그래도 타이핑이 적고 좀 더 견고하고 건조합니다.


14
소스 코드에서 변수 이름을 반복하는 것은 DRY 원칙과 아무 관련이 없습니다. DRY는 "모든 지식은 시스템 내에서 하나의 모호하지 않은 권위있는 표현을 가져야합니다"또는 다른 말로, 지식 의 반복 ( 텍스트 의 반복이 아님)을 피해야한다고 말합니다 .
pedromanoel

4
건조를 위반하는 것이 가장 확실합니다. 변수 이름을 불필요하게 반복하면 다른 형태의 건식과 같은 방식으로 모든 악이 발생합니다. 더 많은 의존성과 더 많은 일을 만듭니다. 위의 예에서 A의 이름을 바꾸면 습식 버전에는 3 가지 변경 사항이 필요하며 3 가지 중 하나라도 빠지면 오류가 디버그됩니다.
Anfurny

1
모든 메소드 호출이 서로 가깝기 때문에이 예제에서 지적한 문제를 볼 수 없습니다. 또한 한 줄에서 변수 이름을 변경하지 않으면 컴파일러에서 오류를 반환하고 프로그램을 실행하기 전에 오류가 수정됩니다. 또한 변수 이름은 선언 범위로 제한됩니다. 이 변수가 전역 변수가 아니라면 이미 나쁜 프로그래밍 방법입니다. IMO, DRY는 타이핑을 줄이는 것이 아니라 물건을 격리시키는 것입니다.
pedromanoel

"컴파일러"는 오류를 반환하거나 PHP 또는 JS를 사용 중일 수 있으며이 조건에 도달하면 런타임에 인터프리터가 오류를 발생시킬 수 있습니다.
Anfurny

1

나는 가독성을 악화 시킨다고 생각하기 때문에 일반적으로 메소드 체인을 싫어합니다. 간결함은 종종 가독성과 혼동되지만 같은 용어가 아닙니다. 하나의 문장으로 모든 것을하면 컴팩트하지만 여러 문장에서 수행하는 것보다 읽기 쉽지 않습니다 (따라하기가 더 어렵습니다). 사용 된 메소드의 리턴 값이 동일하다는 것을 보증 할 수없는 한, 메소드 체인은 혼동의 원인이됩니다.

1.)

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

vs

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

2.)

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

vs

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

삼.)

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

vs

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

보시다시피, 거의 가까워지지 않습니다. 단일 문장에 줄 바꿈을 추가하여 더 읽기 쉽게 만들고 들여 쓰기를 추가하여 다른 객체에 대해 이야기하고 있음을 분명히해야하기 때문입니다. ID 기반 언어를 사용하려면이 방법 대신 Python을 배우십시오. 대부분의 IDE가 코드를 자동으로 포맷하여 들여 쓰기를 제거한다는 점은 말할 것도 없습니다.

이러한 종류의 체인이 유용 할 수있는 유일한 장소는 CLI에서 스트림을 파이핑하거나 SQL에서 여러 쿼리를 함께 결합하는 것입니다. 둘 다 여러 진술에 대한 가격이 있습니다. 그러나 복잡한 문제를 해결하려면 가격을 지불하고 변수를 사용하여 여러 명령문으로 코드를 작성하거나 bash 스크립트 및 저장 프로 시저 또는 뷰를 작성하는 사람들도 있습니다.

DRY 해석으로 : "지식의 반복이 아닌 지식의 반복을 피하십시오." 첫 번째는 원칙이 의미하는 것이지만 두 번째는 "모든 지식이 하나의 모호하지 않아야합니다. 시스템 내에서의 권위있는 표현 ". 두 번째는 모든 비용에서 소형화이며,이 시나리오에서는 가독성이 떨어지기 때문에이 시나리오에서 중단됩니다. 해당 시나리오에서 느슨한 결합이 더 중요하기 때문에 경계 컨텍스트간에 코드를 복사 할 때 첫 번째 해석은 DDD에 의해 중단됩니다.


0

좋은 점 :

  1. 간결하지만 한 줄에 더 우아하게 넣을 수 있습니다.
  2. 때로는 변수 사용을 피할 수 있으며 때로는 유용 할 수 있습니다.
  3. 더 잘 수행 될 수 있습니다.

나쁜 점 :

  1. 실제로 메소드의 기능에 포함되지 않는 오브젝트의 메소드에 기능을 추가하여 리턴을 구현하고 있습니다. 이미 몇 바이트를 절약하기 위해 이미 가지고있는 것을 반환합니다.
  2. 하나의 체인이 다른 체인으로 이어질 때 컨텍스트 스위치를 숨 깁니다. 컨텍스트가 전환 될 때 명확하다는 점을 제외하고는 getter를 사용하여이를 얻을 수 있습니다.
  3. 여러 줄에 걸쳐 체인을 연결하는 것은보기에 좋지 않으며 들여 쓰기에 적합하지 않으며 일부 연산자 처리 혼동을 일으킬 수 있습니다 (특히 ASI가있는 언어에서).
  4. 연결 된 메소드에 유용한 다른 것을 반환하기를 원할 경우 잠재적으로 문제를 해결하거나 더 많은 문제를 겪을 수 있습니다.
  5. 엄격하게 유형이 지정된 언어의 경우에도 항상 감지 할 수없는 경우에도 편의를 위해 순수하게 편리하게 오프로드하지 않는 엔티티에 제어를 오프로드하고 있습니다.
  6. 성능이 저하 될 수 있습니다.

일반:

좋은 접근 방식은 상황이 발생하거나 특정 모듈이 특히 적합 할 때까지 일반적으로 체인을 사용하지 않는 것입니다.

경우에 따라 특히 포인트 1과 2에서 계량 할 때 체인은 가독성을 심하게 손상시킬 수 있습니다.

어쨌든 다른 접근 방식 (예 : 배열 전달) 대신 기괴한 방법으로 메서드를 혼합하는 대신 (parent.setSomething (). getChild (). setSomething (). getParent (). setSomething ())과 같이 잘못 사용될 수 있습니다.


0

의견 답변

연결의 가장 큰 단점은 독자가 각 메서드가 원본 개체에 어떤 영향을 미치는지 이해하고 모든 메서드가 어떤 형식을 반환하는지 이해하기가 어렵다는 것입니다.

몇 가지 질문 :

  • 체인의 메소드가 새 오브젝트를 리턴합니까, 아니면 동일한 오브젝트가 변경 되었습니까?
  • 체인의 모든 메소드가 동일한 유형을 리턴합니까?
  • 그렇지 않으면 체인의 유형이 변경 될 때 어떻게 표시됩니까?
  • 마지막 방법으로 반환 된 값을 안전하게 버릴 수 있습니까?

대부분의 언어에서 디버깅은 실제로 체인화가 더 어려울 수 있습니다. 체인의 각 단계가 자체 라인에 있어도 (체인의 목적을 상실한 경우), 각 단계 이후에 리턴 된 값, 특히 변경되지 않는 메소드의 경우 검사하기가 어려울 수 있습니다.

식은 해결하기가 훨씬 더 복잡하므로 언어 ​​및 컴파일러에 따라 컴파일 시간이 느려질 수 있습니다.

나는 모든 것과 마찬가지로 체인은 일부 시나리오에서 유용 할 수있는 좋은 솔루션이라고 생각합니다. 주의를 기울여 의미를 이해하고 체인 요소 수를 몇 개로 제한해야합니다.


0

형식이 지정된 언어 (부족 auto하거나 이에 상응하는)에서 구현자는 중간 결과의 유형을 선언하지 않아도됩니다.

import Participant
import Schedule

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

더 긴 체인의 경우 여러 가지 다른 중간 유형을 처리 할 수 ​​있으므로 각각을 선언해야합니다.

이 접근법은 a) 모든 함수 호출이 멤버 함수 호출이며 b) 명시 적 유형이 필요한 Java에서 실제로 개발되었다고 생각합니다. 물론 여기에는 명백 함을 잃어 버리는 용어가 있습니다. 그러나 어떤 상황에서는 일부 사람들이 가치가 있다고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.