언제 의존성 역전 원칙을 적용하지 않습니까?


43

현재 SOLID를 알아 내려고 노력 중입니다. 따라서 Dependency Inversion Principle은 두 클래스가 직접이 아닌 인터페이스를 통해 통신해야 함을 의미합니다. 예 : class A메소드가있는 경우 유형의 객체에 대한 포인터를 예상하면 class B이 메소드는 실제로 유형의 객체를 기대해야합니다 abstract base class of B. 이것은 열기 / 닫기에도 도움이됩니다.

내가 올바르게 이해했다면, 내 질문은 이것을 모든 클래스 상호 작용 에 적용하는 것이 좋은 습관 인지 아니면 레이어 측면에서 생각해야 합니까?

내가 회의적 인 이유는 우리 가이 원칙을 준수하기 위해 약간의 가격을 지불하기 때문입니다. 기능을 구현해야한다고 가정 해보십시오 Z. 분석 후, 나는 기능은 결론 Z기능으로 구성 A, BC. 내가 만드는 외관 클래스 Z, 즉 인터페이스를 통해, 클래스를 사용 A, B하고 C. 나는 구현을 코딩을 시작하고 어떤 점에서 나는 작업이 실현 Z실제로 기능 구성 A, BD. 이제 C인터페이스, C클래스 프로토 타입 을 폐기하고 별도의 D인터페이스와 클래스를 작성해야합니다. 인터페이스가 없으면 클래스 만 교체하면됩니다.

다시 말해, 무언가를 바꾸려면 1. 발신자 2. 인터페이스 3. 선언 4. 구현을 변경해야합니다. 파이썬 직접 결합 구현에서는 구현 변경해야합니다 .


13
의존성 역전은 단순히 기술이므로 필요할 때만 적용해야합니다. 적용 가능한 정도에는 제한이 없으므로 어디에서나 적용하면 쓰레기로 끝납니다. 다른 모든 상황과 마찬가지로 특정 기술.
Frank Hileman

간단히 말해 일부 소프트웨어 설계 원칙의 적용은 요구 사항이 변경 될 때 무자비하게 리팩터링 할 수 있는지에 달려 있습니다. 그 중 인터페이스 부분은 디자인의 계약 불변을 가장 잘 포착하는 것으로 여겨지는 반면, 소스 코드 (구현)는 더 빈번한 변경을 허용 할 것으로 예상됩니다.
rwong

@rwong 인터페이스는 계약 불변을 지원하는 언어를 사용하는 경우에만 계약 불변을 캡처합니다. 공통 언어 (Java, C #)에서 인터페이스는 단순히 API 서명 세트입니다. 불필요한 인터페이스를 추가하면 설계가 저하됩니다.
Frank Hileman

나는 당신이 그것을 잘못 이해했다고 말할 것입니다. DIP는 다른 컨텍스트에서 상위 레벨 구성 요소를 재사용 할 수 있도록 "하위 레벨"구성 요소에서 "낮은 레벨"구성 요소로 컴파일 타임 종속성을 피하는 것입니다. 레벨 구성 요소; 이것은 하위 수준 구성 요소에 의해 구현되는 고급 수준에서 추상 형식을 만들어서 수행됩니다. 따라서 높은 수준의 구성 요소와 낮은 수준의 구성 요소는 모두이 추상화에 의존합니다. 결국, 상위 및 하위 레벨 구성 요소는 인터페이스를 통해 통신하지만 이것이 DIP의 본질 은 아닙니다 .
Rogério

답변:


87

많은 만화 나 다른 매체에서 선과 악의 세력은 종종 천사와 악마가 캐릭터의 어깨에 앉아 있습니다. 우리의 이야기에서 선과 악 대신에 우리는 한쪽 어깨에 SOLID를 가지고 있고 다른쪽에 YAGNI (당신은 그것을 필요로하지 않을 것입니다!)를 가지고 있습니다.

최대의 SOLID 원칙은 거대하고 복잡한 초 구성 가능한 엔터프라이즈 시스템에 가장 적합합니다. 더 작거나 더 구체적인 시스템의 경우, 추상화하는 데 시간이 도움이되지 않기 때문에 모든 것을 엄청나게 유연하게 만드는 것은 적절하지 않습니다.

구체적 클래스 대신 인터페이스를 전달한다는 것은 예를 들어 네트워크 스트림을 위해 파일에서 읽기를 쉽게 교환 할 수 있다는 것을 의미합니다. 그러나 소프트웨어 프로젝트의 큰 금액, 유연성의 종류는 아니다 이제까지 필요로 할 것, 그리고 당신은뿐만 아니라 단지 콘크리트 파일 클래스를 통과하고 오늘은 이만하고 뇌 세포를 절약 할 수 있습니다.

소프트웨어 개발 기술의 일부는 시간이 지남에 따라 변할 가능성과 변하지 않는 것에 대해 잘 이해하고 있습니다. 변경 될 수있는 사항에 대해서는 인터페이스 및 기타 SOLID 개념을 사용하십시오. 그렇지 않은 경우 YAGNI를 사용하고 구체적인 유형을 전달하고 팩토리 클래스를 잊고 모든 런타임 연결 및 구성을 잊어 버리고 많은 SOLID 추상화를 잊어 버리십시오. 내 경험상 YAGNI 접근 방식은 그렇지 않은 것보다 훨씬 더 정확한 것으로 입증되었습니다.


19
SOLID에 대한 첫 번째 소개는 약 15 년 전에 우리가 구축하고있는 새로운 시스템에 관한 것이 었습니다. 우리는 모두 쿨 원조를 마셨다. 누군가 야 그니처럼 들리는 것을 언급했다면 , 우리는 "Pfffft ... plebeian"과 같았습니다. 나는 그 시스템이 다음 10 년에 걸쳐 진화하는 것을 지켜 보는 영광 (공포?)을 가졌다. 창업자조차도 아무도 이해할 수없는 다루기 힘든 혼란이되었습니다. 건축가는 SOLID를 좋아합니다. 실제로 생계를 유지하는 사람들은 YAGNI를 좋아합니다. 둘 다 완벽하지는 않지만 YAGNI는 완벽에 더 가까우며, 현재하고있는 일을 모른다면 기본값이되어야합니다. :-)
Calphool

11
@NWard 그래, 우리는 프로젝트에서 그렇게 했어. 그것으로 견과류를 갔다. 이제 우리의 테스트는 부분적으로 과잉 조롱으로 인해 읽거나 유지하는 것이 불가능합니다. 게다가 의존성 주입으로 인해 무언가를 알아 내려고 할 때 코드를 탐색하는 것이 고통 스럽습니다. SOLID는 은색 총알이 아닙니다. YAGNI는 은색 총알이 아닙니다. 자동화 된 테스트는 은총이 아닙니다. 당신이하고있는 일에 대해 열심히 생각 하고, 그것이 당신의 일이나 다른 사람을 도울 것인지 방해 할 것인지에 대한 결정내리는 것을 막을 수있는 것은 아무것도 없습니다 .
jpmc26

22
여기에 많은 안티 솔리드 정서가 있습니다. SOLID와 YAGNI는 스펙트럼의 두 끝이 아닙니다. 차트의 X 및 Y 좌표와 같습니다. 좋은 시스템은 불필요한 코드 (YAGNI)가 거의 없으며 SOLID 원칙을 따릅니다.
Stephen

31
Meh, (a) SOLID = enterprisey에 동의하지 않으며 (b) SOLID의 요점은 우리가 필요로하는 것에 대한 예측이 매우 열악하다는 경향이 있다는 것입니다. 나는 @Stephen에 동의해야합니다. YAGNI는 오늘날 명확하게 나와 있지 않은 미래의 요구 사항을 예상해서는 안된다고 말합니다. SOLID는 시간이 지남에 따라 디자인이 발전하고이를 촉진하기 위해 간단한 기술을 적용해야한다고 말합니다. 그들은 하지 상호 배타적; 둘 다 변화하는 요구 사항에 적응하는 기술입니다. 실제 문제는 불분명하거나 매우 먼 요구 사항에 맞게 설계하려고 할 때 발생합니다.
Aaronaught

8
"네트워크 스트림을 위해 파일에서 읽기를 쉽게 교환 할 수 있습니다"-DI에 대한 지나치게 단순화 된 설명으로 인해 사람들이 길을 잃는 좋은 예입니다. 사람들은 때때로 (실제로)이 방법은을 사용 File하므로 대신 IFile작업을 수행 해야한다고 생각합니다 . 그런 다음 인터페이스를 과다하게 요구했기 때문에 네트워크 스트림을 쉽게 대체 할 수 없으며IFile 소켓에 적용되지 않는 메소드의 작업 도 소켓에 적용되지 않으므로 소켓이 구현할 수 없습니다 IFile. 것들 중 하나는 DI 오른쪽 추상화 (인터페이스) :-) 발명되고,위한 묘책 아니다
스티브 Jessop

11

평신도의 말로 :

DIP를 적용하는 것은 쉽고 재미 있습니다. 첫 번째 시도에서 디자인을 제대로 얻지 못한다고해서 DIP를 완전히 포기한 이유는 충분하지 않습니다.

  • 일반적으로 IDE는 이러한 종류의 리팩토링을 수행하는 데 도움이되며 일부는 이미 구현 된 클래스에서 인터페이스를 추출 할 수도 있습니다.
  • 처음으로 디자인을 얻는 것은 거의 불가능합니다
  • 일반적인 작업 과정에는 개발의 첫 단계에서 인터페이스를 변경하고 다시 생각하는 과정이 포함됩니다
  • 개발이 발전함에 따라 개발이 성숙 해지고 인터페이스를 수정할 이유가 줄어 듭니다.
  • 고급 단계에서는 인터페이스 (디자인)가 완성되어 거의 변경되지 않습니다.
  • 그 순간부터 앱이 확장 가능하기 때문에 이점을 누리기 시작합니다.

다른 한편으로, 인터페이스와 OOD를 사용한 프로그래밍은 때때로 오래된 프로그래밍 기술에 기쁨을 가져다 줄 수 있습니다.

어떤 사람들은 그것이 복잡성을 더한다고 말하지만 opossite는 사실이라고 생각합니다. 소규모 프로젝트에도 마찬가지입니다. 테스트 / 조롱이 쉬워집니다. case명령문이 있거나 중첩 된 경우 코드의 수가 줄어 듭니다 ifs. 순환 복잡성을 줄이고 새로운 방식으로 생각할 수 있습니다. 실제 설계 및 제조와 프로그래밍을 유사하게 만듭니다.


5
OP에서 어떤 언어 또는 IDE가 사용되고 있는지 알지 못하지만 VS 2013에서는 인터페이스에 대해 작업하고 인터페이스를 추출하여 구현하는 것이 엄청나게 간단하며 TDD가 사용되는 경우 중요합니다. 이러한 원칙을 사용하여 개발하는 데 추가 개발 오버 헤드가 없습니다.
stephenbayer 2019

질문이 DIP에 관한 것이면 왜이 답변이 DI에 대해 이야기하고 있습니까? DIP는 1990 년대의 개념이며 DI는 2004 년의 개념입니다.
Rogério

1
(이전 의견은 다른 대답을위한 것이 었습니다. 무시하십시오.) "인터페이스 프로그래밍"은 DIP보다 훨씬 일반적이지만 모든 클래스를 별도의 인터페이스로 구현하는 것은 아닙니다. 또한 테스트 / mocking 도구에 심각한 제한이있는 경우 "테스트 / mocking"이 더 쉬워집니다.
Rogério

@ Rogério 일반적으로 DI를 사용할 때 모든 클래스가 별도의 인터페이스를 구현하는 것은 아닙니다. 여러 클래스에 의해 구현되는 하나의 인터페이스가 일반적입니다.
Tulains Córdova

@ Rogério 나는 DI를 언급 할 때마다 나는 DIP를 의미하면서 대답을 수정했다.
Tulains Córdova의

9

의미있는 곳에 의존성 반전을 사용하십시오 .

극단적 인 반례 중 하나는 많은 언어에 포함 된 "문자열"클래스입니다. 기본 개념, 본질적으로 문자 배열을 나타냅니다. 이 핵심 클래스를 변경할 수 있다고 가정하면 내부 상태를 다른 것으로 바꿀 필요가 없으므로 DI를 사용하는 것은 의미가 없습니다.

다른 모듈에 노출되지 않거나 다른 곳에서 재사용하지 않는 모듈에 내부적으로 사용되는 개체 그룹이있는 경우 DI를 사용하는 것이 좋습니다.

내 의견으로는 DI를 자동으로 사용해야하는 두 가지 장소가 있습니다.

  1. 확장을 위해 설계된 모듈에서 . 모듈의 전체 목적이 모듈을 확장하고 동작을 변경하는 것이라면 DI를 처음부터 굽는 것이 좋습니다.

  2. 코드 재사용을 위해 리팩토링중인 모듈에서. 아마도 클래스를 코딩하여 무언가를 수행 한 다음 나중에 리 팩터를 사용하여 다른 곳에서 해당 코드를 활용할 수 있으며 그렇게해야한다는 것을 깨달을 것입니다 . 이는 DI 및 기타 확장 성 변경의 훌륭한 후보입니다.

여기의 키 는 추가 복잡성을 가져 오기 때문에 필요한 곳에 사용되며 기술 요구 사항 (포인트 1) 또는 정량적 코드 검토 (포인트 2)를 통해 필요를 측정해야 합니다.

DI는 훌륭한 도구이지만 다른 도구와 마찬가지로 남용되거나 오용 될 수 있습니다.

* 위 규칙의 예외 : 왕복 톱은 모든 작업에 완벽한 도구입니다. 문제가 해결되지 않으면 제거됩니다. 영구적으로.


3
"문제"가 벽의 구멍 인 경우 어떻게해야합니까? 톱은 그것을 제거하지 않았다. 그것은 더 나빠질 것입니다. ;)
Mason Wheeler

3
강력하고 재미있는 톱을 사용하는 @MasonWheeler, "벽의 구멍"은 유용한 자산 인 "출입구"로 바뀔 수 있습니다 :-)

1
구멍에 패치를 만들기 위해 톱을 사용할 수 없습니까?
JeffO

사용자가 확장 할 수없는 String형식을 사용하면 몇 가지 장점이 있지만 형식에 적절한 가상 작업 집합이있는 경우 대체 표현이 도움이되는 경우가 많이 있습니다 (예 : 하위 문자열을 지정된 부분에 복사 short[], substring은 ASCII 만 포함하거나 ASCII 만 포함 할 수 있습니다. ASCII 만 포함하는 것으로 간주되는 substring을의 지정된 부분에 복사하십시오 byte[].
supercat

1
질문이 DIP에 관한 것이면 왜이 답변이 DI에 대해 이야기하고 있습니까? DIP는 1990 년대의 개념이며 DI는 2004 년의 개념입니다.
Rogério

5

원래 질문에 DIP의 요점이 빠져있는 것 같습니다.

내가 회의적 인 이유는 우리 가이 원칙을 준수하기 위해 약간의 가격을 지불하기 때문입니다. 예를 들어, 기능 Z를 구현해야합니다. 분석 후 기능 Z는 기능 A, B 및 C로 구성되어 있다고 결론을 내립니다. 인터페이스를 통해 클래스 A, B 및 C를 사용하는 Fascade 클래스 Z를 작성합니다. 구현과 어떤 시점에서 작업 Z가 실제로 기능 A, B 및 D로 구성되어 있음을 알고 있습니다. 이제 C 인터페이스, C 클래스 프로토 타입을 스크랩하고 별도의 D 인터페이스와 클래스를 작성해야합니다. 인터페이스가 없으면 클래스 만 교체해야했습니다.

DIP를 진정으로 활용하려면 먼저 클래스 Z를 작성하고 클래스 A, B 및 C (아직 개발되지 않은)의 기능을 호출해야합니다. 클래스 A, B 및 C에 대한 API를 제공합니다. 그런 다음 클래스 A, B 및 C를 작성하고 세부 사항을 입력하십시오. 클래스 Z가 필요한 클래스를 기반으로 클래스 Z를 작성할 때 필요한 추상화를 효과적으로 작성해야합니다. 클래스 A, B 또는 C가 작성되기 전에 클래스 Z 주위에 테스트를 작성할 수도 있습니다.

DIP는 "높은 수준의 모듈은 낮은 수준의 모듈에 의존해서는 안되며 둘 다 추상화에 의존해야한다"고 말합니다.

클래스 Z에 필요한 것과 필요한 것을 얻는 방법을 익힌 후에는 세부 사항을 채울 수 있습니다. 물론 Z 클래스를 변경해야 할 때도 있지만 99 %는 그렇지 않습니다.

Z가 작성되기 전에 A, B 및 C가 필요하다는 점을 해결했기 때문에 클래스 D는 존재하지 않습니다. 요구 사항의 변화는 완전히 다른 이야기입니다.


5

짧은 대답은 "거의 절대"는 아니지만 실제로 DIP가 의미가없는 몇 가지 장소가 있습니다.

  1. 객체를 생성하는 것이 팩토리 또는 빌더입니다. 이들은 본질적으로 IoC를 완전히 수용하는 시스템에서 "리프 노드"입니다. 어떤 시점에서는 무언가 실제로 객체를 생성해야하며 그렇게하기 위해 다른 것에 의존 할 수 없습니다. 많은 언어에서, IoC 컨테이너가이를 수행 할 수 있지만 때로는 구식 방식으로 수행해야합니다.

  2. 데이터 구조와 알고리즘의 구현. 일반적으로 이러한 경우 최적화하는 현저한 특성 (예 : 실행 시간 및 점근 적 복잡성)은 사용중인 특정 데이터 유형에 따라 다릅니다. 해시 테이블을 구현하는 경우 연결된 목록이 아닌 스토리지 배열을 사용하고 있다는 사실을 알아야하며 테이블 자체만으로 배열을 올바르게 할당하는 방법을 알고 있습니다. 또한 가변 배열을 전달하고 싶지 않고 호출자가 해당 내용을 처리하여 해시 테이블을 해제하도록하십시오.

  3. 도메인 모델 클래스. 이들은 비즈니스 로직을 구현하며 (대부분의 경우) 한 비즈니스 용 소프트웨어 만 개발하기 때문에 하나의 구현 만하는 것이 좋습니다. 일부 도메인 모델 클래스는 다른 도메인 모델 클래스를 사용하여 구성 될 수 있지만 일반적으로 사례별로 적용됩니다. 도메인 모델 개체에는 유용하게 조롱 할 수있는 기능이 포함되어 있지 않으므로 DIP에 대한 테스트 가능성이나 유지 관리 이점이 없습니다.

  4. 외부 API로 제공되고 공개적으로 공개하지 않으려는 구현 세부 사항을 가진 다른 오브젝트를 작성해야하는 모든 오브젝트. 이는 "라이브러리 디자인이 응용 프로그램 디자인과 다릅니다"라는 일반적인 범주에 속합니다. 라이브러리 나 프레임 워크는 내부적으로 DI를 자유로이 사용할 수 있지만 결국 실제 작업을 수행해야합니다. 그렇지 않으면 매우 유용한 라이브러리가 아닙니다. 네트워킹 라이브러리를 개발한다고 가정 해 봅시다. 실제로 소비자가 자체 소켓 구현을 제공 할 수 있기를 원하지 않습니다 . 내부적으로 소켓 추상화를 사용할 수 있지만 호출자에게 노출되는 API는 자체 소켓을 생성합니다.

  5. 단위 테스트 및 테스트 복식. 가짜와 그루터기는 한 가지 일을하고 간단하게해야합니다. 의존성 주입을 할 것인지 걱정할 정도로 복잡한 가짜가 있다면 아마도 너무 복잡 할 것입니다 (아마도 너무 복잡한 인터페이스를 구현하기 때문일 것입니다).

더있을 수 있습니다; 이것들은 내가 다소 자주 보는 것들입니다 .


"동적 언어로 작업 할 때마다"어떻습니까?
케빈

아니? JavaScript에서 많은 작업을 수행하고 여전히 동일하게 적용됩니다. 그러나 SOLID의 "O"와 "I"는 약간 흐릿해질 수 있습니다.
Aaronaught

허 ... 오리의 타이핑과 결합 된 파이썬의 일류 타입이 덜 필요하다는 것을 알았습니다.
케빈

DIP는 타입 시스템과는 전혀 관련이 없습니다. 그리고 "일류 유형"은 어떤 방식으로 파이썬에 고유합니까? 무언가를 테스트하고 싶을 때는 의존성을 대체하기 위해 테스트 배가를 대체해야합니다. 이러한 테스트 복식은 인터페이스의 정적 구현 (정적으로 유형이 지정된 언어)이거나 익명의 객체이거나 동일한 메소드 / 기능을 가진 대체 유형일 수 있습니다 (오리진 입력). 두 경우 모두 인스턴스를 실제로 대체 할 방법이 필요합니다.
Aaronaught

1
@Kevin python은 동적 타이핑이나 느슨한 모의를 가진 최초의 언어는 아닙니다. 또한 완전히 관련이 없습니다. 문제는 객체 유형이 아니라 해당 객체가 어떻게 / 어디에 생성되는지입니다. 객체가 자체 의존성을 만들면 퍼블릭 API가 언급하지 않은 클래스 생성자를 스텁 아웃하는 것과 같은 끔찍한 일을 수행하여 구현 세부 사항이 무엇인지 단위 테스트해야합니다. 또한 테스트, 믹싱 동작 및 객체 구성을 잊어 버리면 결합이 엄격 해집니다. 오리 타이핑은 이러한 문제를 해결하지 못합니다.
Aaronaught

2

가치를 제공하지 않는 너무 작은 수준에서 DIP를 적용 할 수있는 일부 징후는 다음과 같습니다.

  • 해당 인터페이스의 단일 구현만으로 C / CImpl 또는 IC / C 쌍이 있습니다.
  • 인터페이스 및 구현의 서명이 일대일로 일치합니다 (DRY 원칙 위반).
  • 자주 C와 CImpl을 동시에 변경합니다.
  • C는 프로젝트 내부에 있으며 프로젝트 외부에서 라이브러리로 공유하지 않습니다.
  • 실제 클래스 대신 인터페이스로 이동하는 Visual Studio의 Eclipse / F12에서 F3에 의해 좌절됩니다.

이것이 당신이보고있는 것이라면 Z를 C로 직접 호출하고 인터페이스를 건너 뛰는 것이 좋습니다.

또한 의존성 주입 / 동적 프록시 프레임 워크 (Spring, Java EE)에 의한 메소드 장식은 진정한 SOLID DIP와 같은 방식으로 생각하지 않습니다. Java EE 커뮤니티는 이전 과 같이 Foo / FooImpl 쌍이 필요하지 않은 개선 사항으로 간주합니다 ( reference ). 대조적으로, 파이썬은 함수 장식을 일급 언어 기능으로 지원합니다.

이 블로그 게시물을 참조하십시오 .


0

항상 종속성을 반전 시키면 모든 종속성이 거꾸로 나타납니다. 즉, 의존성 매듭으로 지저분한 코드로 시작하면 여전히 (실제로) 거꾸로 된 것입니다. 구현에 대한 모든 변경 사항도 인터페이스를 변경해야한다는 문제가 발생합니다.

의존성 역전 지점은 사물을 복잡하게 만드는 의존성을 선택적으로 뒤집는 것입니다. A에서 B로, C로 가야하는 것은 여전히 ​​그렇습니다. 이제 C에서 A로 갔던 것은 이제 A에서 C로갑니다.

결과는주기가없는 종속성 그래프 여야합니다 (DAG). 있습니다 다양한 이 속성을 확인하고 그래프를 그릴 것입니다.

자세한 설명은 이 기사를 참조 하십시오 .

Dependency Inversion Principle을 올바르게 적용하는 본질은 다음과 같습니다.

의존하는 코드 / 서비스 /…를 인터페이스와 구현으로 나누십시오. 인터페이스는 그것을 사용하여 코드의 전문 용어에 대한 의존성을 재구성하고, 구현은 기본 기술 측면에서 구현합니다.

구현은 그대로 유지됩니다. 그러나 인터페이스는 현재 다른 기능을 가지고 있으며 사용하는 코드가 할 수있는 것을 설명하는 다른 전문 용어 / 언어를 사용합니다. 해당 패키지로 이동하십시오. 인터페이스와 구현을 동일한 패키지에 배치하지 않으면 (의 방향) 종속성이 사용자 → 구현 → 구현 → 사용자로 반전됩니다.

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