문제

내가 올바르게 이해했다면 종속성 주입의 일반적인 메커니즘은 클래스 생성자를 통해 또는 클래스의 공용 속성(멤버)을 통해 주입하는 것입니다.

이는 주입되는 종속성을 노출시키고 캡슐화의 OOP 원칙을 위반합니다.

이 절충안을 식별하는 것이 정확합니까?이 문제를 어떻게 처리합니까?

아래 내 질문에 대한 내 답변도 참조하세요.

도움이 되었습니까?

해결책

이 문제를 흥미롭게 볼 수 있는 또 다른 방법이 있습니다.

IoC/종속성 주입을 사용할 때는 OOP 개념을 사용하지 않습니다.물론 우리는 '호스트'로 OO 언어를 사용하고 있지만 IoC의 기본 아이디어는 OO가 아닌 구성 요소 지향 소프트웨어 엔지니어링에서 나옵니다.

구성 요소 소프트웨어는 종속성 관리에 관한 것입니다. 일반적으로 사용되는 예로는 .NET의 어셈블리 메커니즘이 있습니다.각 어셈블리는 자신이 참조하는 어셈블리 목록을 게시하므로 실행 중인 애플리케이션에 필요한 부분을 함께 모으고 유효성을 검사하는 것이 훨씬 더 쉬워집니다.

IoC를 통해 OO 프로그램에 유사한 기술을 적용함으로써 프로그램을 보다 쉽게 ​​구성하고 유지 관리할 수 있도록 하는 것이 목표입니다.종속성을 게시하는 것(생성자 매개변수 등)이 이것의 핵심 부분입니다.구성 요소/서비스 지향 세계에서는 세부 정보가 누출될 '구현 유형'이 없으므로 캡슐화는 실제로 적용되지 않습니다.

불행하게도 우리 언어는 현재 세분화된 객체 지향 개념과 거친 구성 요소 지향 개념을 분리하지 않으므로 이는 여러분이 마음속에만 두어야 할 차이점입니다. :)

다른 팁

좋은 질문이지만 어느 시점에서 가장 순수한 형태로 캡슐화 필요합니다 대상이 의존성을 충족시키기 위해서는 위반됩니다. 일부 의존성 제공 업체 ~ 해야 하다 문제의 대상이 필요하다는 것을 모두 알고 있습니다 Foo, 제공자는 Foo 대상에.

전형적 으로이 후자의 경우는 생성자 인수 또는 세터 방법을 통해 말한대로 처리됩니다. 그러나 이것이 반드시 사실은 아닙니다. 예를 들어 Java의 Spring DI 프레임 워크의 최신 버전은 개인 필드를 주석에 맡길 수 있습니다 (예 : @Autowired) 그리고 종속성은 모든 클래스 공개 방법/생성자를 통해 종속성을 노출 할 필요없이 반사를 통해 설정됩니다. 이것은 당신이 찾고 있던 솔루션의 종류 일 수 있습니다.

즉, 나는 생성자 주입이 많은 문제라고 생각하지 않습니다. 나는 항상 건설 후에 물체가 완전히 유효해야한다고 생각하여, 자신의 역할을 수행하기 위해 필요한 모든 것이 (즉, 유효한 상태에 있음) 어쨌든 생성자를 통해 공급되어야한다고 생각했습니다. 공동 작업자가 작동하도록 요구하는 객체가있는 경우, 생성자 가이 요구 사항을 공개적으로 광고하고 클래스의 새로운 인스턴스가 만들어지면 충족되도록 보장하는 것이 좋습니다.

이상적으로는 객체를 다룰 때, 당신은 어쨌든 인터페이스를 통해 그들과 상호 작용하고, 더 많이할수록 (그리고 DI를 통해 의존성을 사용하면) 실제로 생성자를 직접 처리해야 할 필요가 줄어 듭니다. 이상적인 상황에서 코드는 구체적인 클래스 사례를 다루거나 만들지 않습니다. 그래서 그것은 단지 주어집니다 IFoo DI를 통해 FooImpl 그것은 그것이 일을해야하며, 사실조차 알지 못한다는 것을 나타냅니다. FooImpl의 존재. 이 관점에서 캡슐화는 완벽합니다.

이것은 물론 의견이지만, 내 마음에 DI는 반드시 캡슐화를 위반하지는 않으며 실제로 필요한 내부에 대한 필요한 모든 지식을 한 곳으로 중앙 집중화함으로써 도움을 줄 수 있습니다. 이것은 그 자체로는 좋은 일 일뿐 만 아니라,이 장소가 자신의 코드베이스 외부에 있으므로, 당신이 작성하는 코드 중 어느 것도 클래스의 종속성에 대해 알아야 할 필요가 없습니다.

이것은 주입되는 종속성을 노출시키고 캡슐화의 OOP 원칙을 위반합니다.

솔직히 말하면 모든 것이 캡슐화를 위반합니다. :) 그것은 잘 대우되어야하는 일종의 부드러운 원칙입니다.

그렇다면 캡슐화를 위반하는 것은 무엇입니까?

계승 하다.

"상속은 부모의 구현에 대한 세부 사항에 서브 클래스를 노출시키기 때문에 종종 '상속 중단 캡슐화'라고 말합니다. (1995 년 4 월 4 일 : 19)

측면 지향 프로그래밍 하다. 예를 들어, MethodCall () 콜백을 등록하면 일반 메소드 평가에 코드를 주입하여 이상한 부작용을 추가 할 수있는 좋은 기회를 제공합니다.

C ++의 친구 선언 하다.

루비의 수업 연장 하다. 문자열 클래스가 완전히 정의 된 후 어딘가에 문자열 메소드를 재정의하십시오.

글쎄, 많은 것들 하다.

캡슐화는 훌륭하고 중요한 원칙입니다. 그러나 유일한 사람은 아닙니다.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}

예, DI는 캡슐화 ( "정보 숨기기"라고도 함)를 위반합니다.

그러나 실제 문제는 개발자가 키스 (짧고 단순하게 유지)와 Yagni (필요하지 않을 것임) 원칙을 위반하기위한 변명으로 사용할 때 발생합니다.

개인적으로, 나는 간단하고 효과적인 솔루션을 선호합니다. 나는 주로 "새로운"연산자를 사용하여 필요한 곳과 어디에 있든 상태의 종속성을 인스턴스화합니다. 간단하고 잘 캡슐화되고 이해하기 쉽고 테스트하기 쉽습니다. 그래서 왜 안돼?

우수한 Depenancy Injection 컨테이너/시스템은 생성자 주입을 허용합니다. 종속 객체는 캡슐화되며 공개적으로 노출 될 필요는 없습니다. 또한, DP 시스템을 사용함으로써, 코드 중 어느 것도 객체가 구성되는 방식에 대한 세부 사항을 "알고"조차도 구성중인 객체를 포함하더라도 아무도 모른다. 거의 모든 코드가 캡슐화 된 객체에 대한 지식으로부터 보호 될뿐만 아니라 개체 구성에 참여하지 않기 때문에이 경우에는 더 많은 캡슐화가 있습니다.

이제 생성 된 객체가 자체 캡슐화 된 객체, 대부분 생성자에있는 경우와 비교한다고 가정합니다. DP에 대한 나의 이해는 우리 가이 책임을 대상에서 벗어나 다른 사람에게주고 싶다는 것입니다. 이를 위해이 경우 DP 컨테이너 인 "다른 사람"은 캡슐화를 "위반"하는 친밀한 지식을 가지고 있습니다. 이점은 그것이 그 지식을 물체에서 쫓아내는 것입니다. 누군가가 그것을 가져야합니다. 나머지 신청서는 그렇지 않습니다.

이런 식으로 생각할 것입니다. 종속 주입 컨테이너/시스템은 캡슐화를 위반하지만 코드는 그렇지 않습니다. 실제로, 귀하의 코드는 더 "캡슐화 된"것입니다.

캡슐화를 위반하지 않습니다. 공동 작업자를 제공하고 있지만 수업은 사용 방법을 결정하게됩니다. 당신이 따르는 한 묻지 말라고 말하십시오 상황은 괜찮습니다. 제작물 주입이 바람직하다는 것을 알지만 세터는 스마트 한 경우에도 괜찮을 수 있습니다. 즉, 클래스가 나타내는 불변량을 유지하기위한 논리가 포함되어 있습니다.

이것은 Upvoted 답변과 비슷하지만 큰 소리로 생각하고 싶습니다. 아마도 다른 사람들도 이런 식으로 물건을보고 싶습니다.

  • Classical OO는 생성자를 사용하여 클래스 소비자를위한 공개 "초기화"계약을 정의합니다 (모든 구현 세부 정보 숨기기; 일명 캡슐화). 이 계약은 인스턴스화 후 즉시 사용 가능한 객체 (즉, 사용자가 기억할 추가 초기화 단계를 기억할 수 없음)를 보장 할 수 있습니다.

  • (생성자) DI 출혈에 의한 캡슐화를 부인할 수 없습니다 구현 세부 사항 이 공개 생성자 인터페이스를 통해. 사용자의 초기화 계약을 정의 할 책임이있는 공개 생성자를 여전히 고려하는 한, 캡슐화의 끔찍한 위반을 만들었습니다.

이론적 예 :

수업 foo 4 가지 방법이 있으며 초기화를 위해 정수가 필요하므로 생성자가 foo (int size) 그리고 수업 사용자에게 즉시 분명합니다 foo 그들이 제공해야한다 크기 FOO가 작동하기 위해 인스턴스화에.

FOO 의이 특정 구현에도 필요할 수 있습니다. iwidget 일을하기 위해. 이 종속성의 생성자 주입은 우리가 foo (int size, iwidget 위젯)

이것에 대해 저를 어지럽히는 것은 이제 우리에게는 생성자가 있습니다. 블렌딩 종속성이있는 초기화 데이터 - 클래스 사용자에게 하나의 입력이 관심이 있습니다 (크기), 다른 하나는 사용자를 혼동하는 데 도움이되고 구현 세부 사항입니다 (위젯).

크기 매개 변수는 종속성이 아닙니다. 간단한 인스턴스 초기화 값입니다. IOC는 외부 종속성 (위젯과 같은)에 대해서는 Dandy이지만 내부 상태 초기화에는 적합하지 않습니다.

더 나쁜 것은 위젯 이이 클래스의 4 가지 방법 중 2 개에만 필요한 경우 어떻게해야합니까? 위젯이 사용되지 않더라도 인스턴스화 오버 헤드가 발생할 수 있습니다!

이것을 타협/조정하는 방법은 무엇입니까?

한 가지 접근 방식은 전용 인터페이스로 전환하여 작동 계약을 정의하는 것입니다. 사용자가 생성자 사용을 폐지합니다. 일관되게하려면 모든 객체는 인터페이스를 통해서만 액세스해야하며, IOC/DI 컨테이너와 같은 일부 형태의 Resolver를 통해서만 인스턴스화해야합니다. 컨테이너만이 인스턴스화됩니다.

위젯 종속성을 처리하지만 Foo 인터페이스에서 별도의 초기화 메소드에 의지하지 않고 어떻게 "크기"를 초기화합니까? 이 솔루션을 사용하여 인스턴스를 얻을 때까지 FOO 인스턴스가 완전히 초기화되도록 할 수있는 능력을 잃었습니다. Bummer, 나는 정말 좋아하기 때문에 아이디어와 단순성 생성자 주입.

초기화가 외부 종속성 이상인 경우이 DI 세계에서 보장 된 초기화를 어떻게 달성합니까?

Jeff Sternal이 질문에 대한 의견에서 지적했듯이 답은 전적으로 당신이 정의하는 방법에 달려 있습니다. 캡슐화.

캡슐화가 의미하는 바에 대한 두 개의 메인 캠프가있는 것 같습니다.

  1. 객체와 관련된 모든 것은 객체의 메소드입니다. 그래서, a File 객체에는 방법이있을 수 있습니다 Save, Print, Display, ModifyText, 등.
  2. 대상은 그 자체의 작은 세상이며 외부 행동에 의존하지 않습니다.

이 두 정의는 서로 직접적으로 모순됩니다. 만약 File 물체는 자체적으로 인쇄 할 수 있으며 프린터의 동작에 크게 의존합니다. 반면에, 단지 만약 알고 있습니다 그것을 위해 인쇄 할 수있는 것에 대해 (an IFilePrinter 또는 그러한 인터페이스), 그 다음 File 객체는 인쇄에 대해 아무것도 알 필요가 없으므로 작업하면 객체에 의존성이 줄어 듭니다.

따라서 첫 번째 정의를 사용하면 종속성 주입이 캡슐화를 중단합니다. 그러나 솔직히 내가 첫 번째 정의를 좋아하는지 모르겠습니다. 분명히 확장되지 않습니다 (MS Word는 하나의 큰 클래스가 될 것입니다).

반면에 의존성 주입은 거의 있습니다 필수적인 캡슐화의 두 번째 정의를 사용하는 경우

순수한 캡슐화는 결코 달성 할 수없는 이상입니다. 모든 종속성이 숨겨져 있다면 DI가 필요하지 않을 것입니다. 이런 식으로 생각하십시오. 예를 들어 자동차 객체의 속도의 정수 값과 같은 객체 내에서 내재화 할 수있는 개인 값이 있다면 외부 의존성이 없으며 그 종속성을 반전 시키거나 주입 할 필요가 없습니다. 순전히 개인 기능으로 작동하는 이러한 종류의 내부 상태 값은 항상 캡슐화하고 싶은 것입니다.

그러나 특정 종류의 엔진 객체를 원하는 자동차를 만들면 외부 의존성이 있습니다. 내부적으로 자동차 객체의 생성자 내에서 캡슐화를 보존하지만 콘크리트 클래스 GmoverheadCamengine에 훨씬 더 교활한 커플 링을 생성하거나 자동차 객체가 작동하도록 허용 할 수 있습니다. 예를 들어 콘크리트 의존성이없는 인터페이스 iengine과 같은 신자 적으로 (그리고 훨씬 더 강력하게). IOC 컨테이너 또는 간단한 DI를 사용하든이를 달성하든이 점은 요점이 아닙니다. 요점은 요점이 아닙니다. 요점은 여러 종류의 엔진을 사용할 수없는 자동차를 가지고있어 코드베이스를보다 유연하고 더 유연하게 만들 수 있습니다. 부작용이 덜 발생합니다.

DI는 캡슐화 위반이 아니며, 거의 모든 OOP 프로젝트 내에서 캡슐화가 반드시 끊어 질 때 커플 링을 최소화하는 방법입니다. 인터페이스에 의존성을 외부에 주입하면 커플 링 부작용을 최소화하고 클래스가 구현에 대해 불가지론적인 상태를 유지할 수 있습니다.

나는 단순함을 믿습니다.도메인 클래스에 IOC/종속성 주입을 적용해도 관계를 설명하는 외부 XML 파일을 가짐으로써 코드를 메인으로 만들기가 훨씬 더 어려워지는 것 외에는 아무런 개선도 이루어지지 않습니다.EJB 1.0/2.0 및 struts 1.1과 같은 많은 기술은 XML에 넣는 내용을 줄이고 주석 등으로 ​​코드에 넣는 방법으로 되돌리고 있습니다.따라서 개발하는 모든 클래스에 IOC를 적용하면 코드가 의미가 없게 됩니다.

IOC는 종속 객체가 컴파일 타임에 생성 준비가 되지 않은 경우 이점을 제공합니다.이는 다양한 시나리오에서 작동해야 할 수 있는 공통 기본 프레임워크를 설정하려고 시도하면서 대부분의 인프라 추상 수준 아키텍처 구성 요소에서 발생할 수 있습니다.그런 곳에서는 IOC를 사용하는 것이 더 합리적입니다.그럼에도 불구하고 이것이 코드를 더 단순하고 유지 관리하기 쉽게 만들지는 않습니다.

다른 모든 기술과 마찬가지로 이 기술에도 찬반 양론이 있습니다.내 걱정은 최선의 상황 사용법에 관계없이 모든 장소에서 최신 기술을 구현한다는 것입니다.

그것은 종속성이 실제로 구현 세부인지 또는 클라이언트가 어떤 방식 으로든 알고 싶어하는 것인지 또는 다른 방식으로 알고 싶어하는지에 달려 있습니다. 관련된 한 가지는 클래스가 목표로하는 추상화 수준입니다. 여기 몇 가지 예가 있어요.

호출 속도를 높이기 위해 후드 아래 캐싱을 사용하는 메소드가있는 경우 캐시 객체는 싱글 톤이어야하며 ~ 아니다 주입됩니다. 캐시가 전혀 사용되고 있다는 사실은 클래스의 클라이언트가 신경 쓰지 않아도되는 구현 세부 사항입니다.

클래스가 데이터 스트림을 출력 해야하는 경우 출력 스트림을 주입하여 클래스가 결과를 배열, 파일 또는 다른 사람이 데이터를 보내려는 곳에 쉽게 출력 할 수 있도록하는 것이 합리적 일 수 있습니다.

회색 영역의 경우 Monte Carlo 시뮬레이션을 수행하는 수업이 있다고 가정 해 봅시다. 무작위성의 원천이 필요합니다. 한편으로, 이것이 필요하다는 사실은 클라이언트가 무작위성의 출처를 정확히 신경 쓰지 않는다는 점에서 구현 세부 사항입니다. 반면에, 실제 임의의 숫자 생성기는 클라이언트가 제어하기를 원할 수있는 무작위성, 속도 등 사이에 트레이드 오프를하기 때문에 클라이언트는 반복 가능한 행동을 얻기 위해 시드를 제어하기를 원할 수 있으므로 주입이 합리적 일 수 있습니다. 이 경우 임의의 숫자 생성기를 지정하지 않고 클래스를 만드는 방법을 제공하고 스레드-로컬 싱글 톤을 기본값으로 사용하는 것이 좋습니다. 더 미세한 제어의 필요성이 발생하는 경우, 임의성 원을 주입 할 수있는 다른 생성자를 제공하십시오.

캡슐화는 클래스에 객체를 작성해야 할 책임이있는 경우 (구현 세부 사항에 대한 지식이 필요) 한 다음 클래스를 사용하는 경우 (이러한 세부 사항에 대한 지식이 필요하지 않음). 이유를 설명하겠습니다. 그러나 먼저 빠른 자동차 혐기학 :

내가 1971 년의 kombi를 운전할 때, 나는 가속기를 누를 수 있었고 더 빨리 갔다. 나는 이유를 알 필요가 없었지만 공장에서 Kombi를 지은 사람들은 이유를 정확히 알고있었습니다.

그러나 코딩으로 돌아갑니다. 캡슐화 "해당 구현을 사용하여 구현 세부 사항을 숨기고 있습니다." 캡슐화는 클래스의 사용자없이 구현 세부 정보가 변경 될 수 있기 때문에 좋은 일입니다.

종속성 주입을 사용하는 경우 생성자 주입을 사용하여 구성됩니다. 서비스 객체를 입력하십시오 (모델 상태의 엔티티/값 객체와 반대). 서비스 유형 객체의 모든 멤버 변수는 누출되지 않아야 할 구현 세부 사항을 나타냅니다. 예 : 소켓 포트 번호, 데이터베이스 자격 증명, 암호화를 수행하기 위해 호출 할 다른 클래스, 캐시 등

그만큼 건설자 수업이 처음 만들어 질 때 관련이 있습니다. 이것은 DI 컨테이너 (또는 공장)가 모든 서비스 객체를 함께 전선하는 동안 구조 상 중에 발생합니다. DI 컨테이너는 구현 세부 사항에 대해서만 알고 있습니다. Kombi Factory의 사람들이 점화 플러그에 대해 알고있는 것과 같은 구현 세부 사항에 대해 모두 알고 있습니다.

런 타임에서 생성 된 서비스 객체는 실제 작업을 수행하기 위해 Apon이라고합니다. 현재 객체의 발신자는 구현 세부 사항에 대해 아무것도 모릅니다.

그게 제가 해변으로 내 kombi를 운전하는 것입니다.

이제 캡슐화로 돌아갑니다. 구현 세부 사항이 변경되면 런타임에 해당 구현을 사용하는 클래스가 변경 될 필요가 없습니다. 캡슐화가 깨지지 않았습니다.

나는 새 차를 해변으로 운전할 수 있습니다. 캡슐화가 깨지지 않았습니다.

구현 세부 사항이 변경되면 DI 컨테이너 (또는 공장)가 변경해야합니다. 공장에서 처음부터 구현 세부 정보를 숨기려고하지 않았습니다.

이 문제에 대해 조금 더 어려움을 겪었을 때, 나는 이제 의존성 주입이 (현재) 캡슐화를 어느 정도 위반한다는 견해를 가지고 있습니다. 그래도 잘못 이해하지 마십시오. 종속성 주입을 사용하는 것이 대부분의 경우 트레이드 오프의 가치가 있다고 생각합니다.

DI가 캡슐화를 위반하는 이유는 작업중인 구성 요소가 "외부"파티 (고객을위한 라이브러리 작성을 생각하는 것)로 전달 될 때 명확 해집니다.

내 구성 요소가 생성자 (또는 공개 속성)를 통해 하위 구성 요소를 주입 해야하는 경우

"사용자가 구성 요소의 내부 데이터를 유효하지 않거나 일관되지 않은 상태로 설정하지 못하게합니다".

동시에 그렇게 말할 수는 없습니다

"구성 요소 (다른 소프트웨어) 사용자는 구성 요소가 무엇을하는지 알아야하며, 그것이 어떻게하는지에 대한 세부 사항에 의존 할 수는 없습니다.".

두 인용문 모두에서 왔습니다 위키 백과.

구체적인 예를 제시하려면 : WCF 서비스 (본질적으로 원격 외관)와의 의사 소통을 단순화하고 숨기는 클라이언트 측 DLL을 제공해야합니다. 3 가지 다른 WCF 프록시 클래스에 의존하기 때문에 DI 접근법을 취하면 생성자를 통해 노출해야합니다. 그것으로 나는 숨기려고하는 의사 소통 계층의 내부를 노출시킨다.

일반적으로 나는 Di를위한 전부입니다. 이 특별한 (극단적 인) 예에서, 그것은 나를 위험한 것처럼 맞이합니다.

나는이 개념으로도 어려움을 겪었다. 처음에는 DI 컨테이너 (예 : 스프링)를 사용하여 후프를 통해 점프하는 것처럼 느껴지는 '요구 사항'은 물체를 인스턴스화합니다. 그러나 실제로는 실제로 후프가 아닙니다. 필요한 객체를 만드는 또 다른 '게시 된'방법 일뿐입니다. 물론, 캡슐화는 클래스 외부의 누군가가 무엇을 필요로하는지 알고 있지만 '캡슐화는'깨졌습니다. 그러나 실제로는 DI 컨테이너라는 것을 아는 다른 시스템은 아닙니다. 한 객체가 다른 객체가 필요하기 때문에 마술 같은 일은 다르게 일어나지 않습니다.

사실 공장과 리포지토리에 집중함으로써 DI가 전혀 관련되어 있음을 알 필요조차 없습니다! 나에게 뚜껑을 캡슐화에 다시 넣습니다. 아휴!

추신. 를 제공함으로써 의존성 주입반드시 그런 것은 아닙니다 부서지다 캡슐화. 예시:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

클라이언트 코드는 구현 세부 사항을 여전히 알지 못합니다.

어쩌면 이것은 순진한 사고 방식이지만 정수 매개 변수를 취하는 생성자와 매개 변수로 서비스를 취하는 생성자의 차이점은 무엇입니까? 이것은 새 객체 밖에서 정수를 정의하고 객체에 공급하는 것이 캡슐화를 중단한다는 것을 의미합니까? 서비스가 새 객체 내에서만 사용되는 경우 캡슐화가 어떻게 파괴되는지 알 수 없습니다.

또한 일종의 자동화 기능 (예 : C#의 자동)를 사용하면 코드를 매우 깨끗하게 만듭니다. Autofac Builder의 확장 방법을 구축함으로써 종속성 목록이 커짐에 따라 시간이 지남에 따라 유지해야 할 많은 DI 구성 코드를 잘라낼 수있었습니다.

나는 최소한 DI가 캡슐화를 크게 약화 시킨다는 것이 자립적이라고 생각합니다. 그에 따라 여기에 고려해야 할 DI의 다른 단점이 있습니다.

  1. 코드를 재사용하기가 더 어려워집니다. 클라이언트가 의존성을 명시 적으로 제공하지 않고 클라이언트가 사용할 수있는 모듈은 클라이언트가 해당 구성 요소의 종속성이 무엇인지 알아 내고 어떻게 든 사용할 수있게하는 것보다 사용하기 쉽습니다. 예를 들어 ASP 응용 프로그램에서 사용되도록 원래 생성 된 구성 요소는 클라이언트 HTTP 요청과 관련된 수명을 제공하는 DI 컨테이너가 제공하는 종속성을 가질 수 있습니다. 원래 ASP 응용 프로그램과 동일한 내장 DI 컨테이너와 함께 제공되지 않는 다른 클라이언트에서는 재생산이 간단하지 않을 수 있습니다.

  2. 코드가 더 취약해질 수 있습니다. 인터페이스 사양에 의해 제공되는 종속성은 예기치 않은 방식으로 구현 될 수 있으며, 이는 정적으로 해결 된 콘크리트 의존성으로 불가능한 전체 런타임 버그를 일으킨다.

  3. 코드가 덜 유연하게 만들 수 있습니다.이를 통해 작동하는 방식에 대한 선택이 줄어들 수 있습니다. 모든 클래스가 소유 인스턴스의 전체 수명 동안 존재하는 모든 종속성을 가질 필요는 없지만 많은 DI 구현에는 다른 옵션이 없습니다.

그 점을 염두에두고 가장 중요한 질문은 그렇다고 생각합니다. "특정 의존성은 외부 적으로 지정되어야합니까?". 실제로 나는 테스트를 지원하기 위해 외부 적으로 공급되는 의존성을 만들어야한다는 사실을 거의 발견하지 못했습니다.

의존성이 진정으로 외부로 제공되어야하는 경우, 일반적으로 객체 간의 관계는 내부 의존성이 아닌 협업임을 시사합니다.이 경우 적절한 목표는 다른 클래스 내부의 한 클래스를 캡슐화하는 대신 클래스.

내 경험상 DI 사용에 관한 주요 문제는 DI가 내장 된 응용 프로그램 프레임 워크로 시작하든 코드베이스에 DI 지원을 추가하든 관계없이 사람들은 DI 지원이 올바른 것으로 가정합니다. 인스턴스화하는 방법 모든 것. 그들은 단지 "이 의존성이 외부 적으로 지정되어야합니까?"라는 질문을하지 않아도됩니다. 더 나쁜 것은 또한 다른 사람들이 DI 지원을 사용하도록 강요하기 시작합니다. 모든 것 도.

그 결과 코드베이스가 코드베이스에서 무엇이든 인스턴스를 생성하는 상태로 이용되기 시작한다는 것입니다. 그리고 무엇이든 인스턴스화 된 곳.

그래서 질문에 대한 나의 대답은 이것입니다. 당신이 해결하는 실제 문제를 식별 할 수있는 곳에서 DI를 사용하십시오.

DI는 비공유 객체에 대한 캡슐화 - 기간을 위반합니다.공유 객체는 생성되는 객체 외부의 수명을 가지므로 생성되는 객체에 집합되어야 합니다.생성되는 객체에 대한 전용 객체는 생성된 객체로 구성되어야 합니다. 생성된 객체가 삭제되면 구성된 객체도 함께 가져옵니다.인체를 예로 들어보겠습니다.무엇이 구성되고 무엇이 집계되는지.DI를 사용한다면 인체 생성자는 100개의 객체를 갖게 됩니다.예를 들어, 많은 장기는 (잠재적으로) 교체 가능합니다.그러나 그것들은 여전히 ​​몸 속에 구성되어 있습니다.혈액 세포는 외부 영향(단백질 제외) 없이 매일 체내에서 생성되고 파괴됩니다.따라서 혈액 세포는 신체 내부에서 생성됩니다(new BloodCell()).

DI 옹호자들은 객체가 절대로 new 연산자를 사용해서는 안 된다고 주장합니다.이러한 "순수주의적" 접근 방식은 캡슐화를 위반할 뿐만 아니라 객체를 생성하는 사람에 대한 Liskov 대체 원칙도 위반합니다.

나는 극단적 인 DI가 캡슐화를 위반할 수 있다는 데 동의합니다. 일반적으로 DI는 결코 진정으로 캡슐화되지 않은 종속성을 노출시킵니다. 다음은 Miško Hevery의 단순화 된 예입니다. 싱글 톤은 병리학 적 거짓말 쟁이입니다:

신용 카드 테스트로 시작하여 간단한 단위 테스트를 작성합니다.

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

다음 달에는 $ 100의 청구서가 있습니다. 왜 청구 되었습니까? 단위 테스트는 생산 데이터베이스에 영향을 미쳤습니다. 내부적으로 신용 카드 전화 Database.getInstance(). refactoring 신용 카드가 필요합니다 DatabaseInterface 생성자에서 의존성이 있다는 사실을 드러냅니다. 그러나 신용 카드 클래스가 외부로 눈에 띄는 부작용을 일으키기 때문에 의존성이 시작되기 시작하지 않았다고 주장합니다. 리팩토링없이 신용 카드를 테스트하려면 분명히 종속성을 관찰 할 수 있습니다.

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

데이터베이스를 종속성으로 노출시키는 것이 좋은 디자인이기 때문에 캡슐화를 줄이는지 걱정할 가치가 없다고 생각합니다. 모든 DI 결정이 그렇게 간단한 것은 아닙니다. 그러나 다른 답변 중 어느 것도 카운터 예제를 보여줍니다.

나는 그것이 범위의 문제라고 생각합니다. 캡슐화를 정의 할 때 (방법을 알려주지 않음) 캡슐화 된 기능을 정의해야합니다.

  1. 그대로 수업: 당신이 캡슐화하고있는 것은 수업의 유일한 책임입니다. 그것이하는 방법을 알고있는 것. 예를 들어, 정렬. 주문을 위해 일부 비교기를 주입한다면, 클라이언트는 캡슐화 된 것의 일부가 아닙니다 : QuickSort.

  2. 구성된 기능: 즉시 사용 가능한 기능을 제공하려면 QuickSort 클래스가 아니라 비교기로 구성된 QuickSort 클래스 인스턴스를 제공합니다. 이 경우 사용자 코드에서 숨겨져 있어야하는 작성 및 구성을 담당하는 코드입니다. 그리고 그것이 캡슐화입니다.

클래스를 프로그래밍 할 때는 클래스에 단일 책임을 구현하면 옵션 1을 사용하고 있습니다.

당신이 응용 프로그램을 프로그래밍 할 때, 그것은 유용한 것을 만드는 것을 만들고 있습니다. 콘크리트 일을하면 옵션 2를 사용하여 상환합니다.

이것은 구성된 인스턴스의 구현입니다.

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

이것이 다른 클라이언트 코드를 사용하는 방법입니다.

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

구현을 변경하면 캡슐화됩니다 (변경 clientSorter Bean 정의) 클라이언트 사용을 중단하지 않습니다. 어쩌면 모두 함께 쓰여진 XML 파일을 사용하면 모든 세부 사항이 보입니다. 그러나 고객 코드 (Client Code)를 믿습니다ClientService) 모릅니다 분류기에 대해서는 아무것도 없습니다.

아마 그것을 언급 할 가치가있을 것입니다 Encapsulation 다소 관점에 의존합니다.

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

누군가의 관점에서 A 두 번째 예에서 클래스 A 의 본질에 대해 훨씬 덜 알고 있습니다 this.b

반면, di없이

new A()

vs

new A(new B())

이 코드를보고있는 사람은 A 두 번째 예에서.

DI를 사용하면 적어도 유출 된 지식이 한 곳에 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top