불변 객체에 대한 인터페이스를 선언하지 마십시오


27

불변 객체에 대한 인터페이스를 선언하지 마십시오

[편집] 해당 객체가 DTO (Data Transfer Object) 또는 POD (Plain Old Data)를 나타내는 경우

합리적인 지침입니까?

지금까지 변경 불가능한 봉인 클래스에 대한 인터페이스를 만들었습니다 (데이터를 변경할 수 없음). 불변성에 관심이있는 곳에서는 인터페이스를 사용하지 않도록 조심하려고 노력했습니다.

불행히도, 인터페이스가 코드에 퍼지기 시작합니다 (그리고 내가 걱정하는 코드는 아닙니다). 인터페이스가 전달 된 다음 전달 된 것이 불변이라고 가정하려는 코드로 전달하려고합니다.

이 문제로 인해 불변의 객체에 대한 인터페이스를 선언하지 않는 것을 고려하고 있습니다.

이것은 단위 테스트와 관련하여 파급 효과가있을 수 있지만 그 외에는 합리적인 지침으로 보입니까?

아니면 내가보고있는 "확산 인터페이스"문제를 피하기 위해 사용해야하는 다른 패턴이 있습니까?

(나는 여러 가지 이유로이 불변의 객체를 사용하고 있습니다 : 주로 많은 멀티 스레드 코드를 작성하기 때문에 스레드 안전성을 위해; 그러나 메소드에 전달 된 객체의 방어 복사본을 만들 수 없기 때문에 코드가 훨씬 간단 해집니다. 인터페이스를 사용했다면 불변 인 것을 아는 많은 경우가 있습니다. 실제로 인터페이스를 제공하지 않으면 인터페이스를 통해 참조되는 객체의 방어 복사본을 만들 수도 없습니다. 복제 작업 또는 직렬화 방법 ...)

[편집하다]

객체를 불변으로 만들고 싶은 이유에 대해 더 많은 컨텍스트를 제공하려면 Eric Lippert의이 블로그 게시물을 참조하십시오.

http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/

또한 멀티 스레드 작업 대기열에서 조작 / 전달되는 항목과 같은 하위 수준 개념으로 작업하고 있음을 지적해야합니다. 이들은 본질적으로 DTO입니다.

또한 Joshua Bloch 는 그의 책 Effective Java에서 불변 객체를 사용할 것을 권장합니다 .


후속 조치

의견 주셔서 감사합니다. 계속해서이 가이드 라인을 DTO 및 그들의 ilk에 사용하기로 결정했습니다. 지금까지는 잘 작동하지만 일주일 밖에 걸리지 않았지만 여전히 좋아 보입니다.

이것에 관해 내가 묻고 싶은 다른 문제들이 있습니다; 특히 내가 "심해 또는 얕은 불변성"이라고 부르는 것 (딥 및 얕은 복제에서 훔친 명명법)-그것은 또 다른 질문입니다.


3
+1 좋은 질문입니다. 객체가 단지 동일한 인터페이스를 구현하는 것이 아닌 특정 불변 클래스의 것이 왜 중요한지 설명하십시오. 다른 곳에서 문제가 발생한 것일 수 있습니다. Dependency Injection은 단위 테스트에 매우 중요하지만 코드에 특정 불변 클래스를 요구하도록 강요하면 다른 많은 이점이 있습니다.
Steven Doggart

1
불변 이라는 용어를 사용하면 약간 혼란 스럽습니다 . 명확하게 말하면 상속 및 재정의 할 수없는 봉인 클래스를 의미합니까, 또는 노출 된 데이터가 읽기 전용이며 객체가 생성되면 변경할 수 없음을 의미합니까? 다시 말해, 객체가 특정 유형이거나 그 값이 절대 변하지 않는다는 것을 보증하고 싶습니까? 첫 번째 의견에서 나는 전자를 의미한다고 가정했지만 이제는 편집하면 후자와 비슷하게 들립니다. 아니면 둘 다에 관심이 있습니까?
Steven Doggart

3
혼란스러워 왜 인터페이스에서 setter를 남겨 두지 않겠습니까? 그러나 다른 한편으로, 나는 실제로 도메인 객체 및 / 또는 DTO가 객체에 대한 팩토리를 요구하는 객체에 대한 인터페이스를 보는 것에 지치지 않습니다.
Michael Brown

2
무엇 클래스 인터페이스에 대한 모든 불변? 예를 들어, 자바의 번호 클래스는 하나가 정의 할 수 있습니다 List<Number>저장할 수있는 Integer, Float, Long, BigDecimal, 등 ... 모두 자신 불변입니다.

1
@MikeBrown 인터페이스가 불변성을 암시한다고해서 구현이 그것을 강제한다는 것을 의미하지는 않습니다. 컨벤션에 그대로 두어도됩니다 (즉, 불변성이 요구되는 인터페이스를 문서화 할 수 있습니다). 누군가 컨벤션을 위반하면 실제로는 매우 불쾌한 문제가 발생할 수 있습니다.
vaughandroid

답변:


17

내 의견으로는, 당신의 규칙은 좋은 것입니다 (적어도 나쁜 것은 아닙니다). 그러나 당신이 묘사하는 상황 때문입니다. 나는 모든 상황에서 그것에 동의한다고 말하지 않을 것입니다. 그래서 내 내부 교육자의 관점에서, 당신의 규칙이 기술적으로 너무 광범위하다고 말해야 할 것입니다.

일반적으로 불변 개체는 기본적으로 데이터 전송 개체 (DTO)로 사용되지 않는 한 정의하지 않습니다. 즉, 데이터 속성은 포함하지만 논리는 거의없고 종속성은 없습니다. 그렇다면 여기에있는 것처럼 인터페이스 대신 콘크리트 유형을 직접 사용하는 것이 안전하다고 말하고 싶습니다.

나는 동의하지 않는 단위 테스팅 순수 주의자들이있을 것이라고 확신하지만, DTO 수업은 단위 테스팅 및 의존성 주입 요구 사항에서 안전하게 제외 될 수 있다고 생각합니다. 종속성이 없기 때문에 팩토리를 사용하여 DTO를 작성할 필요가 없습니다. 모든 것이 필요에 따라 DTO를 직접 생성하는 경우 실제로 다른 유형을 주입 할 방법이 없으므로 인터페이스가 필요하지 않습니다. 논리가 포함되어 있지 않기 때문에 단위 테스트 할 것이 없습니다. 의존성이없는 한 논리가 포함되어 있어도 필요한 경우 논리를 단위 테스트하는 것이 쉽지 않습니다.

따라서 모든 DTO 클래스가 인터페이스를 구현하지 않아야한다는 규칙을 만드는 것은 잠재적으로 불필요하지만 소프트웨어 디자인에 해를 끼치 지 않을 것이라고 생각합니다. 데이터를 변경할 수 없어야한다는 요구 사항이 있으며 인터페이스를 통해이를 적용 할 수 없으므로 해당 규칙을 코딩 표준으로 설정하는 것이 합법적이라고 말하고 싶습니다.

그러나 더 큰 문제는 깨끗한 DTO 레이어를 엄격하게 적용해야한다는 것입니다. 인터페이스가없는 불변 클래스가 DTO 계층에만 존재하고 DTO 계층에 로직과 종속성이없는 한 안전합니다. 그러나 계층을 혼합하기 시작하고 비즈니스 계층 클래스의 두 배인 인터페이스가없는 클래스가 있다면 많은 문제가 발생하기 시작합니다.


특히 DTO 또는 불변 값 객체를 처리 할 것으로 예상되는 코드는 인터페이스의 기능보다는 해당 유형의 기능을 사용해야하지만 이는 해당 클래스에 의해 구현되고 인터페이스에 대한 사용 사례가 없다는 의미는 아닙니다. 다른 클래스에서는 최소한의 시간 동안 유효한 값을 반환 할 수있는 메서드를 사용합니다 (예 : 인터페이스가 함수에 전달되는 경우 해당 함수가 반환 될 때까지 메서드가 유효한 값을 반환해야 함). 이러한 인터페이스는 유사한 데이터와 원하는 것을 캡슐화하는 여러 유형이있는 경우 유용 할 수 있습니다.
supercat

... 서로 데이터를 복사하는 수단을 갖습니다. 특정 데이터를 포함하는 모든 유형이 데이터를 읽기 위해 동일한 인터페이스를 구현하는 경우, 각각의 데이터를 다른 유형에서 가져 오는 방법을 몰라도 이러한 데이터를 모든 유형간에 쉽게 복사 할 수 있습니다.
supercat

이 시점에서 게터 (그리고 어리석은 이유로 DTO를 변경할 수있는 경우 세터)를 제거하고 필드를 공개 할 수 있습니다. 당신은 거기에 어떤 논리도 넣지 않을 것입니다.
Kevin

@Kevin 가정하지만 자동 속성의 현대적인 편리함 때문에 속성을 쉽게 만들 수 있습니다. 성능과 효율성이 가장 중요한 관심사라면 아마도 중요 할 것입니다. 어쨌든 DTO에서 어떤 수준의 "논리"를 허용합니까? 속성에 유효성 검사 로직을 추가하더라도 모의가 필요한 종속성이없는 한 단위 테스트를 수행하는 데 어려움이 없습니다. DTO가 종속성 비즈니스 오브젝트를 사용하지 않는 한 테스트 할 수 있기 때문에 약간의 논리가 내장되어있는 것이 안전합니다.
Steven Doggart

개인적으로 나는 가능한 한 그런 것들을 깨끗하게 유지하는 것을 선호하지만 규칙을 구부릴 필요가있는 경우가 항상 있으므로 경우를 대비하여 문을 열어 두는 것이 좋습니다.
Steven Doggart

7

나는 코드를 오용하기 어렵게 만드는 것에 대해 소란스러워했다. 나는 변경 멤버를 숨기고 읽기 전용 인터페이스를 만들었고, 내 일반적인 서명 등에 많은 제약을가했습니다. 가상의 동료를 믿지 않기 때문에 디자인 결정을 내렸을 때가 대부분이었습니다. "언젠가 그들은 새로운 엔트리 레벨의 남자를 고용 할 것이며 클래스 XYZ가 DTO ABC를 업데이트 할 수 없다는 것을 알지 못할 것입니다." 다른 경우에 나는 나무를 통해 숲을 보지 못하는 명백한 해결책을 무시하고 잘못된 문제에 집중하고있었습니다.

더 이상 DTO를위한 인터페이스를 만들지 않습니다. 나는 제 코드를 만지는 사람들 (주로 나 자신)이 무엇이 허용되고 무엇이 의미가 있는지를 알고 있다고 가정합니다. 똑같은 바보 같은 실수를 계속하면 보통 인터페이스를 강화하려고하지 않습니다. 이제 나는 왜 같은 실수를 계속하는지 이해하려고 대부분의 시간을 보냅니다. 일반적으로 내가 과도하게 분석하거나 핵심 개념을 놓치고 있기 때문입니다. 편집증을 포기한 이후로 코드 작업이 훨씬 쉬워졌습니다. 또한 시스템 작업에 대한 내부 지식이 필요한 "프레임 워크"가 줄어 듭니다.

내 결론은 가장 간단한 것을 찾는 것입니다. 안전한 인터페이스를 만드는 복잡성의 추가로 개발 시간이 낭비되고 간단한 코드가 복잡해집니다. 라이브러리를 사용하는 10,000 명의 개발자가있을 때 이와 같은 것에 대해 걱정하십시오. 나를 믿으십시오. 불필요한 긴장에서 벗어날 것입니다.


나는 일반적으로 단위 테스트를 위해 의존성 주입이 필요할 때 인터페이스를 저장합니다.
트래비스 파크

5

괜찮은 지침처럼 보이지만 이상한 이유가 있습니다. 인터페이스 (또는 추상 기본 클래스)가 불변의 일련의 객체에 균일하게 액세스 할 수있는 곳이 많이 있습니다. 전략은 여기에 빠지는 경향이 있습니다. 상태 객체는 여기에 속하는 경향이 있습니다. 불변의 것처럼 보이도록 인터페이스를 구성하고 API에서와 같이 문서화하는 것이 너무 불합리하다고 생각하지 않습니다.

즉, 사람들은 Plain Old Data (이하 POD) 객체, 심지어 단순한 (불변의) 구조를 지나치게 인터페이스하는 경향이 있습니다. 코드에 기본적인 구조에 대한 대안이 없다면 인터페이스가 필요하지 않습니다. 아니요, 단위 테스트는 설계를 변경하기에 충분한 이유가 아닙니다 (데이터베이스 액세스를 모의하는 것은 인터페이스를 제공하는 이유가 아니며 향후 변경을위한 유연성입니다). 기본 기본 구조를 그대로 사용하십시오.


2

불변의 것을 알면 많은 경우 코드가 훨씬 간단 해집니다. 인터페이스를 사용해 본 적이없는 경우입니다.

액세서 구현자가 걱정해야 할 문제라고 생각하지 않습니다. 경우 interface X불변하기위한 것입니다, 그것은 그들이 불변의 방식으로 인터페이스를 구현하는 것을 보장하기 인터페이스 구현의 책임 아닌가요?

그러나 내 마음에는 불변 인터페이스와 같은 것이 없습니다. 인터페이스에 의해 지정된 코드 계약은 객체의 노출 된 메소드에만 적용되며 객체의 내부에는 영향을 미치지 않습니다.

불변성을 인터페이스가 아닌 데코레이터로 구현하는 것이 훨씬 일반적이지만 해당 솔루션의 실행 가능성은 실제로 객체의 구조와 구현의 복잡성에 달려 있습니다.


1
데코레이터로 불변성을 구현하는 예제를 제공 할 수 있습니까?
vaughandroid

아니 사소, 나는 생각하지 않는다, 그러나 그것은 비교적 간단한 생각 운동의 - 경우 MutableObjectn변화 상태와 것을 방법 m방법은 반환 상태 것을 ImmutableDecorator돌려 상태 (방법을 노출 계속할 수 있습니다 m), 그리고, 환경, 어설에 따라 또는 변경 가능한 메소드 중 하나가 호출되면 예외가 발생합니다.
Jonathan Rich

컴파일 타임 확실성을 런타임 예외 가능성으로 변환하는 것을 정말로 좋아하지 않습니다 ...
Matthew Watson

그러나 주어진 메소드가 상태를 변경하는지 여부를 ImmutableDecorator가 어떻게 알 수 있습니까?
vaughandroid

인터페이스를 구현하는 클래스가 인터페이스에서 정의한 메소드와 관련하여 변경할 수없는 경우 확실하지 않습니다. @Baqueta 데코레이터는 기본 클래스의 구현에 대한 지식이 있어야합니다.
Jonathan Rich

0

인터페이스가 전달 된 다음 전달 된 것이 불변이라고 가정하려는 코드로 전달하려고합니다.

C #에서 "불변"유형의 개체를 예상하는 메서드는 C # 인터페이스가 불변성 계약을 지정할 수 없으므로 인터페이스 유형의 매개 변수를 가져서는 안됩니다. 따라서 C #에서 제안하는 지침은 처음부터 수행 할 수 없기 때문에 의미가 없습니다 (향후의 언어 버전에서는이를 수행 할 수 없음).

귀하의 질문은 불변성에 대한 Eric Lippert의 기사에 대한 미묘한 오해에서 비롯됩니다. 에릭은 인터페이스를 정의하지 않았다 IStack<T>IQueue<T>불변의 스택과 큐에 대한 계약을 지정할 수 있습니다. 그들은하지 않습니다. 그는 편의상 그것들을 정의했다. 이러한 인터페이스를 통해 빈 스택 및 대기열에 대해 서로 다른 유형을 정의 할 수있었습니다. 빈 스택을 나타 내기 위해 인터페이스 또는 별도의 유형을 요구하지 않고 단일 유형을 사용하여 불변 스택의 다른 디자인 및 구현 을 제안 할 수 있지만 결과 코드는 깨끗하지 않고 조금 덜 효율적입니다.

이제 Eric의 디자인을 고수합시다. 불변 스택을 필요로하는 메소드 는 일반적인 의미에서 스택의 추상 데이터 유형을 나타내는 Stack<T>일반 인터페이스 IStack<T>가 아닌 유형의 매개 변수를 가져야합니다 . Eric의 불변 스택을 사용할 때이 작업을 수행하는 방법은 명확하지 않으며 기사에서이 내용을 다루지 않았지만 가능합니다. 빈 스택 유형에 문제가 있습니다. 빈 스택을 얻지 않도록하여이 문제를 해결할 수 있습니다. 더미 값을 스택의 첫 번째 값으로 푸시하고 절대로 팝하지 않으면이를 보장 할 수 있습니다. 이 방법을 사용하면 안전의 결과 캐스트 할 수 PushPop로를 Stack<T>.

Stack<T>구현이 IStack<T>유용 할 수 있습니다. 반드시 불변 스택이 아닌 스택, 모든 스택이 필요한 메소드를 정의 할 수 있습니다. 이러한 메소드는 유형의 매개 변수를 가질 수 있습니다 IStack<T>. 이를 통해 불변 스택을 전달할 수 있습니다. 이상적으로 IStack<T>는 표준 라이브러리 자체의 일부입니다. .NET에는는 IStack<T>없지만 불변 스택이 구현할 수있는 다른 표준 인터페이스가 있으므로 유형이 더 유용합니다.

앞에서 언급 한 불변 스택의 대체 설계 및 구현에는이라는 인터페이스가 사용 IImmutableStack<T>됩니다. 물론 인터페이스 이름에 "Immutable"을 삽입한다고해서이를 구현하는 모든 유형을 변경할 수있는 것은 아닙니다. 그러나이 인터페이스에서 불변의 계약은 언어 적입니다. 좋은 개발자는 그것을 존중해야합니다.

소규모의 내부 라이브러리를 개발하는 경우 팀의 모든 사람과 계약을 이행하기 위해 동의 할 수 IImmutableStack<T>있으며 매개 변수 유형으로 사용할 수 있습니다 . 그렇지 않으면 인터페이스 유형을 사용하지 않아야합니다.

C # 질문에 태그를 추가했기 때문에 C # 사양에는 DTO 및 POD와 같은 것이 없다는 것을 추가하고 싶습니다. 따라서 폐기하거나 정확하게 정의하면 질문이 개선됩니다.

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