클래스 속성이 클래스의 새 인스턴스를 만들어 반환하는 경우 안티 패턴입니까?


24

나는 Heading몇 가지 일을 하는 클래스를 가지고 있지만 현재 제목 값의 반대를 반환 할 수 있어야하며, 결국 Heading클래스 자체 의 새 인스턴스를 생성하여 사용해야 합니다.

reciprocal현재 값의 반대 제목을 반환하기 위해 간단한 속성을 호출 한 다음 수동으로 제목 클래스의 새 인스턴스를 만들거나 제목 클래스 createReciprocalHeading()의 새 인스턴스를 자동으로 만들어서 사용자.

하지만, 내 동료 중 하나는 클래스 만들기 위해 저를 추천 재산 이라고 reciprocal는 getter 메소드를 통해 클래스 자체의 새로운 인스턴스 반환합니다.

내 질문은 : 클래스의 속성이 그렇게 행동하는 것이 안티 패턴이 아닙니까?

특히 다음과 같은 이유로 직관적이지 않습니다.

  1. 내 생각에 클래스의 속성이 클래스의 새 인스턴스를 반환해서는 안되며
  2. 이 속성의 이름은 reciprocal개발자가 IDE의 도움을 받거나 게터 서명을 확인하지 않고도 동작을 완전히 이해하는 데 도움이되지 않습니다.

클래스 속성이해야 할 일이 너무 엄격합니까, 아니면 유효한 관심사입니까? 나는 항상 필드와 속성을 통해 클래스의 상태와 메서드를 통해 동작을 관리하려고 노력했지만 이것이 클래스 속성의 정의에 어떻게 적합한 지 알 수 없습니다.


6
@Ewan이 그의 답변의 마지막 단락에서 말한 것처럼 반 패턴이 Heading아니라 불변 유형 reciprocal으로 새로운 것을 반환하는 Heading것은 "성공의 피"모범 사례입니다. (두 번의 호출에서 경고는 reciprocal"같은 것"을 반환해야한다. 즉, 그들은 평등 테스트를 통과해야한다.)
David Arno

20
"내 수업은 변경할 수 없습니다". 당신의 진짜 반 패턴이 있습니다. ;)
David Arno

3
@DavidArno 글쎄, 때로는 언어가 기본적으로 언어를 지원하지 않는 경우 특정 것들을 불변으로 만드는 데 막대한 성능 저하가 발생하지만, 그렇지 않으면 불변성이 많은 경우에 좋은 습관이라는 데 동의합니다.
53777A

2
@dcorking 클래스는 C #과 TypeScript 모두에서 구현되지만이 언어를 무시합니다.
53777A

2
답변 같은 @Kaiserludi 소리 (좋은 답) - 코멘트 명확성 요청하기위한 있습니다
dcorking

답변:


22

Copy () 또는 Clone ()과 같은 것은 알 수 없지만 그래도 걱정할 것 같습니다.

예를 들어 :

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

이 속성은 매번 새 개체라는 경고를 표시하는 것이 좋습니다.

다음과 같이 가정 할 수도 있습니다.

h2.reciprocal == h1

하나. 표제 클래스가 변경 불가능한 값 유형 인 경우 이러한 관계를 구현할 수 있으며 상호는 조작에 대한 좋은 이름 일 수 있습니다.


1
그냥 점에 유의 Copy()Clone()방법이 아닌 속성입니다. OP가 새 인스턴스를 반환하는 부동산 게터를 참조한다고 생각합니다.
Lynn

6
알아. 그러나 속성은 (다양한) c #의 메소드입니다
Ewan

4
이 경우, C #을 제공하는 훨씬 더있다 : String.Substring(), Array.Reverse(), Int32.GetHashCode(), 등

If your heading class was an immutable value type-불변 객체의 속성 설정자는 불변 객체의 정의이기 때문에 항상 새로운 인스턴스를 반환해야한다고 생각합니다 (적어도 새로운 값이 이전 값과 동일하지 않은 경우). :)
Ivan Kolmychek

17

클래스의 인터페이스는 클래스 사용자가 클래스 작동 방식에 대한 가정을하도록합니다.

이러한 가정 중 많은 것이 맞고 틀린 것이 거의 없다면 인터페이스가 좋습니다.

이러한 가정 중 다수가 틀렸고 옳지 않은 경우가 많으면 인터페이스가 엉망입니다.

속성에 대한 일반적인 가정은 get 함수를 호출하는 것이 저렴하다는 것입니다. 속성에 대한 또 다른 일반적인 가정은 get 함수를 연속으로 두 번 호출하면 같은 것을 반환한다는 것입니다.


기대치를 변경하기 위해 일관성을 사용하여이 문제를 해결할 수 있습니다. 예를 들어, 당신이 필요로하는 작은 3D 라이브러리 Vector, Ray Matrix등 당신은 같은 같은 게터 것을 할 수 있습니다 Vector.normal와는 Matrix.inverse거의 항상 비싸다. 사용자 인터페이스는 지속적으로 비용이 속성을 사용 불행히도 경우에도 인터페이스는 기껏해야 하나 직관적으로 사용됩니다 Vector.create_normal()Matrix.create_inverse()- 아직 난 기대를 변경 한 후, 속성을 사용하여보다 직관적 인 인터페이스를 생성하는 할 수있는 강력한 인수 알고.


10

속성은 매우 빠르게 반환되고 반복 된 호출로 동일한 값을 반환해야하며 값을 가져 오는 데 부작용이 없어야합니다. 여기서 설명하는 내용은 속성으로 구현 해서는 안됩니다 .

직감이 전반적으로 정확합니다. 팩토리 메소드는 반복 호출로 동일한 값을 리턴하지 않습니다. 매번 새로운 인스턴스를 반환합니다. 이것은 재산으로 거의 실격됩니다. 나중에 개발할 때 네트워크 종속성과 같은 인스턴스 생성에 가중치가 추가되는 것도 빠르지 않습니다.

속성은 일반적으로 클래스의 현재 상태의 일부를 가져 오거나 설정하는 매우 간단한 작업이어야합니다. 공장과 유사한 작업은이 기준을 충족하지 않습니다.


1
DateTime.Now속성은 일반적으로 사용되며 새 개체를 나타냅니다. 속성의 이름이 매번 같은 객체가 반환되는지 여부에 대한 모호성을 제거하는 데 도움이 될 수 있다고 생각합니다. 이름과 사용법에 모두 있습니다.
Greg Burghardt

3
DateTime.Now는 실수로 간주됩니다. 참조 stackoverflow.com/questions/5437972/...
브래드 토마스

@GregBurghardt 새로운 객체의 부작용은 그 자체로는 문제가되지 않을 수 있습니다 DateTime. 값 유형 이기 때문에 객체 식별 개념이 없습니다. 올바르게 이해하면 문제는 비 결정적이며 매번 새로운 값을 반환한다는 것입니다.
Zev Spitz

1
@GregBurghardt DateTime.New는 정적이므로 객체의 상태를 나타낼 것으로 기대할 수 없습니다.
Tsahi Asher

5

“속성”을 구성하는 것은 언어 별 질문이며,“속성”의 호출자가 기대하는 것은 언어 별 질문이기 때문에 이것에 대한 언어 독립적 인 대답이 없다고 생각합니다. 나는 이것에 대해 생각하는 가장 유익한 방법은 발신자의 관점에서 어떻게 보이는지 생각하는 것이라고 생각합니다.

C #에서 속성은 (전통적으로) 대문자로 표시되지만 (메소드와 유사하지만) 괄호 (공개 인스턴스 변수와 같이)가 없다는 점에서 차별화됩니다. 설명서가없는 다음 코드가 표시되면 무엇을 기대하십니까?

var reciprocalHeading = myHeading.Reciprocal;

상대 C # 초보자이지만 Microsoft의 Property Usage Guidelines를 읽는 사람 은 다음 Reciprocal과 같은 것들을 기대 합니다.

  1. Heading수업 의 논리적 인 데이터 멤버이다
  2. 내가 값을 캐시 할 필요가 없도록 호출 비용이 저렴합니다.
  3. 관찰 가능한 부작용이 없음
  4. 연속해서 두 번 호출하면 동일한 결과를 생성합니다
  5. (아마) ReciprocalChanged이벤트를 제안하십시오

이 가정들 중 (3)과 (4)는 아마도 맞을 것입니다 ( 이완의 답변Heading 에서와 같이 불변의 값 유형 이라고 가정 ) (어떤 불구하고 의미 론적 의미를 가지고 제목을 아마해야 이벤트). 이것은 C # API에서 "가상 수를 얻거나 계산하는 것"이 속성으로 구현 되어서는 안되며 특히 계산이 저렴하고 불변 인 경우 경계선 인 경우를 제안합니다 .HeadingChangedHeading

(그러나 속성을 호출하면 (2)가 아니라 새 인스턴스를 생성 하는지 여부와 관련이 없습니다. CLR에서 객체를 만드는 것 자체는 꽤 저렴합니다.)

Java에서 특성은 메소드 이름 지정 규칙입니다. 내가 보면

Heading reciprocalHeading = myHeading.getReciprocal();

내 기대는 위의 것과 비슷합니다 (명확하게 명시되지 않은 경우). 나는 전화가 저렴하고 dem 등하 며 부작용이 없을 것으로 기대합니다. 그러나, 자바 빈즈 프레임 워크 외부에서 "속성"의 개념이 자바의 의미, 특히 결코 상응하는 불변의 특성을 고려할 때 모든하지 setReciprocal()getXXX()규칙은 지금은 다소 구식이다. 에서 효과적인 자바 , 두 번째 버전 (현재 이미 8 년 이상 된) :

boolean호출 된 객체 의 비 기능 또는 속성 을 반환하는 메소드 는 일반적으로 명사, 명사구 또는 동사로 시작하는 동사구로 명명됩니다 get… 으로 시작하는 세 번째 형식 만 get허용되지만이 주장에 대한 근거는 거의 없다고 주장하는 성명서가 있습니다. 처음 두 형식은 일반적으로 더 읽기 쉬운 코드로 이어집니다… (p. 239)

그렇다면 현대적이고 유창한 API에서 볼 것으로 예상됩니다.

Heading reciprocalHeading = myHeading.reciprocal();

이것은 호출이 저렴하고 dem 등성이 있으며 부작용이 없음을 다시 제안하지만 새로운 계산이 수행되는지 또는 새 객체가 작성되는지에 대해서는 아무 말도하지 않습니다. 이건 괜찮아; 좋은 API에서는 신경 쓰지 않아야합니다.

루비에는 속성 같은 것이 없습니다. “속성”이 있지만 내가 볼 경우

reciprocalHeading = my_heading.reciprocal

나는 간단한 접근 자 메소드 @reciprocal를 통해 인스턴스 변수에 액세스 attr_reader하는지 또는 비싼 계산을 수행하는 메소드를 호출하는지 여부를 즉시 알 수있는 방법이 없습니다 . 그러나 메소드 이름이 단순한 명사라는 사실은 calcReciprocal다시 말하지만 호출이 적어도 싸고 아마도 부작용이 없음을 시사합니다.

스칼라에서 명명 규칙은 부작용이있는 메소드는 괄호를 사용하고 그렇지 않은 메소드는 그렇지 않지만

val reciprocal = heading.reciprocal

다음 중 하나 일 수 있습니다.

// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …

(주 스칼라는 것을 허용 그럼에도 불구하고 각종 물건 낙담 에 의해 스타일 가이드를 .이 스칼라 내 주요 불만 중 하나입니다.)

괄호가 없으면 전화에 부작용이 없다는 것을 알 수 있습니다. 이 이름은 통화가 상대적으로 저렴해야 함을 나타냅니다. 그 외에도 나는 그것이 어떻게 가치를 얻는 지 상관하지 않습니다 .

한마디로 : 사용중인 언어와 다른 프로그래머가 API에 어떤 기대를 가져다 줄지 알 수 있습니다. 그 밖의 모든 것은 구현 세부 사항입니다.


1
이 글을 작성하는 데 시간을 내 주셔서 감사합니다.
53777A

2

다른 사람들이 말했듯이 동일한 클래스의 인스턴스를 반환하는 것은 다소 일반적인 패턴입니다.

이름 지정은 언어의 이름 지정 규칙과 밀접한 관련이 있습니다.

예를 들어, Java에서는 아마도 그것이 호출 될 것으로 기대합니다. getReciprocal();

즉, 나는 가변 대 불변의 객체를 고려할 것 입니다.

불변의 것을 사용하면 상황이 매우 쉽고 동일한 객체를 반환하거나하지 않아도 상처를 입지 않습니다.

으로 변경할 것, 이것은 아주 무서운 얻을 수 있습니다.

b = a.reciprocal
a += 1
b = what?

이제 b는 무엇을 말하는가? a의 원래 값의 역수? 아니면 변경된 것의 역수? 이것은 예가 너무 짧을 수 있지만 요점을 알 수 있습니다.

이러한 경우 발생하는 상황을 알려주 는 더 나은 이름 을 찾으십시오. 예를 들어 createReciprocal()더 나은 선택 일 수 있습니다.

그러나 그것은 실제로 상황에 달려 있습니다.


당신을 찾으시는 변경 가능 ?
Carsten S

@CarstenS 물론입니다. 감사합니다. 내 머리가 어 idea는지 몰라 :)
Eiko

getReciprocal()"일반"JavaBeans 스타일 게터처럼 "소리"가 나기 때문에 다소 동의하지 않습니다 . 다른 프로그래머에게 뭔가 다른 일이 일어나고 있음을 나타내거나 암시하는 "createXXX ()"또는 "calculateXXX ()"를
선호하십시오

@ user949300 나는 당신의 우려에 다소 동의 하고 create ... 이름을 더 언급했습니다. 그러나 단점도 있습니다. create ...는 항상 그렇지 않을 수있는 새 인스턴스를 의미합니다 (특별한 경우에는 단일 전용 객체를 생각하십시오). 누출 구현 세부 정보를 계산하여 가격표에 대한 잘못된 가정을 장려 할 수 있습니다.
Eiko

1

단일 책임과 명확성을 위해 객체를 가져 와서 그 역을 반환하는 ReverseHeadingFactory가 있습니다. 이것은 새로운 객체가 리턴되고 있음을 더 명확하게 할 것이며, 리버스를 생성하는 코드가 다른 코드로부터 캡슐화되었다는 것을 의미합니다.


1
이름 명확성을 위해 +1이지만 다른 솔루션의 SRP에 어떤 문제가 있습니까?
53777A

3
팩토리 메소드보다 팩토리 클래스를 권장하는 이유는 무엇입니까? (제 생각에, 객체의 유용한 책임은 종종 iterator.next와 같은 자체적 인 객체를 방출하는 것입니다. SRP를
약간만

2
@dcorking 팩토리 클래스와 메소드 (제 생각에)는 일반적으로 "객체를 비교할 수 있습니까, 아니면 비교기를 만들어야합니까?"와 같은 유형의 질문입니다. 비교기를 만드는 것은 항상 괜찮지 만, 그것들을 비교하는 보편적 인 방법이라면 비교할 수 있습니다. (예, 더 큰 숫자가,이 비교에 대한 좋은이며, 작은 것보다 큰하지만, 나는 비교기에 그것을 만들 것 모두 고르게 모든 가능성보다 큰 말할 수) - 그래서이 들어 나는 것 생각 하나가 헤더를 뒤집는 방법을 추가하여 여분의 클래스를 피하는 방법을 만드는 것에 의존합니다.
Captain Man

0

따라 다릅니다. 나는 일반적으로 속성이 인스턴스의 일부인 것을 반환하기를 기대하므로 동일한 클래스의 다른 인스턴스를 반환하는 것은 조금 특이합니다.

그러나 예를 들어 문자열 클래스에는 "firstWord", "lastWord"속성이 있거나 유니 코드 "firstLetter", "lastLetter"를 처리하는 경우 전체 문자열 객체 (일반적으로 작은 객체)가됩니다.


0

다른 답변은 속성 부분을 다루기 때문에 # 2에 대해 설명하겠습니다 reciprocal.

이외의 다른 것을 사용하지 마십시오 reciprocal. 그것은 당신이 묘사하는 것에 대한 유일한 올바른 용어입니다 (그리고 공식적인 용어입니다). 개발자가 작업중인 도메인을 배우지 못하도록 잘못된 소프트웨어를 작성하지 마십시오. 탐색에서 용어는 매우 특정한 의미를 가지며 특정 상황에서 해가 없는 것처럼 보이 reverse거나 opposite혼동을 일으킬 수있는 것을 사용합니다.


0

논리적으로, 그것은 제목의 속성이 아닙니다. 만약 그렇다면, 당신은 또한 숫자의 음수가 그 숫자의 재산이라고 말할 수 있으며, 솔직히 말해서 "재산"이 모든 의미를 잃게 만들면 거의 모든 것이 재산이 될 것입니다.

역수는 표제의 순수한 함수이며, 숫자의 음수는 그 숫자의 순수한 함수입니다.

경험상 설정이 의미가 없다면 속성이 아니어야합니다. 물론 설정은 허용되지 않지만 세터를 추가하는 것은 이론적으로 가능하고 의미가 있어야합니다.

이제 일부 언어에서는 해당 언어의 역학으로 인해 이점이있는 경우 어쨌든 해당 언어의 컨텍스트에 지정된 속성으로 사용하는 것이 좋습니다. 예를 들어, 데이터베이스에 저장하려는 경우 ORM 프레임 워크에서 자동 처리 할 수있는 경우이를 특성으로 만드는 것이 좋습니다. 또는 속성과 매개 변수가없는 멤버 함수 사이에 구별이없는 언어 일 수 있습니다 (그런 언어를 모르지만 일부는 확실합니다). 그런 다음 함수와 속성을 구분하는 것은 API 디자이너와 문서 작성자에게 달려 있습니다.


편집 : C #의 경우 기존 클래스, 특히 표준 라이브러리 클래스를 살펴 보겠습니다.

  • 있다 BigIntegerComplex구조. 그러나 그것들은 구조이며 정적 함수만을 가지며 모든 속성은 다른 유형입니다. Heading수업에 도움이되는 디자인은 그리 많지 않습니다 .
  • Math.NET Numerics에는 Vector클래스 가 거의 없으며 속성이 거의 없으며에 가깝습니다 Heading. 이것으로 가면 속성이 아닌 상호 함수를 갖게됩니다.

코드에서 실제로 사용되는 라이브러리 또는 기존의 유사한 클래스를 살펴볼 수 있습니다. 한 가지 방법이 옳지 않고 다른 방법으로 잘못되면 일관성을 유지하십시오.


-1

실제로 매우 흥미로운 질문입니다. 나는 당신이 제안하는 것에 SOLID + DRY + 키스 위반이 보이지 않지만 여전히 나쁜 냄새가납니다.

클래스의 인스턴스를 반환하는 메서드를 생성자 라고합니다 . 생성자 이외의 메서드에서 새 인스턴스를 만들고 있습니다. 그것은 세상의 끝이 아니지만 클라이언트로서 나는 그것을 기대 하지 않을 입니다. 이것은 일반적으로 특정 상황에서 수행됩니다 : 팩토리 또는 때로는 같은 클래스의 정적 메소드 (싱글 톤 및 객체 지향이 아닌 프로그래머에게 유용합니까?)

플러스 : getReciprocal()반환 된 객체 를 호출하면 어떻게됩니까 ? 클래스의 다른 인스턴스를 얻습니다. 첫 번째 인스턴스의 정확한 사본이 될 수 있습니다! 그리고 getReciprocal()그 중 하나 를 호출하면 ? 사람을 펼치고있는 물건?

다시 : 호출자가 왕복 값을 필요로하면 (스칼라처럼)? getReciprocal()->getValue()? 우리는 작은 이익을 위해 데메테르 법칙을 위반하고 있습니다.


나는 downvoter에서 반론을 기꺼이 받아들입니다, 감사합니다!
Dr. Gianluigi Zane Zanettini

1
나는 공감하지 않았지만 그 이유는 그것이 나머지 답변에 실제로 많은 것을 추가하지는 않지만 실제로 잘못되었을 수 있다고 생각합니다.
53777A

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