생성자 또는 속성 설정자를 통한 종속성 주입?


151

클래스를 리팩토링하고 새로운 의존성을 추가하고 있습니다. 클래스는 현재 생성자에서 기존 종속성을 사용하고 있습니다. 일관성을 위해 생성자에 매개 변수를 추가합니다.
물론 단위 테스트를위한 몇 가지 서브 클래스와 그 밖의 여러 클래스가 있으므로 이제 모든 생성자를 변경하여 게임을 진행하고 있습니다.
setter와 함께 속성을 사용하는 것이 종속성을 얻는 더 좋은 방법이라고 생각합니다. 주입 된 의존성이 클래스의 인스턴스를 구성하는 인터페이스의 일부라고 생각하지 않습니다. 종속성을 추가하면 이제 모든 사용자 (서브 클래스 및 직접 인스턴스화하는 사람)가 갑자기이를 알게됩니다. 캡슐화가 깨지는 것 같습니다.

이것은 기존 코드의 패턴이 아닌 것 같으므로 생성자 대 속성의 장단점에 대한 일반적인 합의가 무엇인지 찾고 있습니다. 속성 설정기를 사용하는 것이 더 낫습니까?

답변:


126

글쎄, 그것은 :-)에 달려 있습니다.

클래스가 종속성없이 작업을 수행 할 수 없으면 생성자에 추가하십시오. 클래스 에는 새로운 종속성이 필요 하므로 변경 사항이 변경되기를 원합니다. 또한 완전히 초기화되지 않은 클래스 ( "2 단계 구성")를 만드는 것은 반 패턴 (IMHO)입니다.

클래스가 종속성없이 작동 할 수 있다면 setter가 좋습니다.


11
나는 많은 경우에 Null Object 패턴을 사용하고 생성자에 대한 참조를 요구하는 것이 바람직하다고 생각합니다. 이것은 모든 널 점검과 증가 된 순환 복잡성을 피합니다.
Mark Lindell

3
@ 마크 : 좋은 지적입니다. 그러나 문제는 기존 클래스에 종속성을 추가하는 것입니다. 그런 다음 인수가없는 생성자를 유지하면 이전 버전과의 호환성이 가능합니다.
sleske

종속성이 작동하는 데 필요한시기는 어떻습니까? 그러나 해당 종속성의 기본 주입으로 충분합니다. 그런 다음 속성이나 생성자 오버로드로 해당 종속성을 "재정의"해야합니까?
Patrick Szalapski

@ 패트릭 : "클래스가 종속성없이 작업을 수행 할 수 없습니다"라는 말은 합리적인 기본값이 없다는 것을 의미했습니다 (예 : 클래스에는 DB 연결이 필요함). 귀하의 상황에서 둘 다 작동합니다. 나는 여전히 복잡성을 줄이기 때문에 생성자 접근 방식을 선택합니다 (예를 들어 setter가 두 번 호출되면 어떻게됩니까?).
sleske


21

클래스의 사용자가되는 가정 주어진 클래스의 종속성에 대해 알고. 예를 들어 데이터베이스에 연결된 클래스가 있고 지속성 계층 종속성을 주입하는 수단을 제공하지 않은 경우 사용자는 데이터베이스에 대한 연결을 사용할 수 있어야한다는 것을 절대 알 수 없습니다. 그러나 생성자를 변경하면 지속성 계층에 대한 종속성이 있음을 사용자에게 알립니다.

또한 이전 생성자의 모든 사용을 변경하지 않으려면 이전 생성자와 새 생성자의 임시 브리지로 생성자 체인을 적용하기 만하면됩니다.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

의존성 주입의 포인트 중 하나는 클래스가 갖는 의존성을 밝히는 것입니다. 클래스가 너무 많은 의존성을 가지고 있다면, 일부 리팩토링이 일어날 시간이다 : 클래스의 모든 메소드가 모든 의존성을 사용 하는가? 그렇지 않다면, 그것은 수업이 어디로 나뉘어 질 수 있는지를보기에 좋은 출발점입니다.


물론 생성자 체이닝은 새 매개 변수에 대한 합리적인 기본값이있는 경우에만 작동합니다. 그러나 그렇지 않으면 당신은 어쨌든 일을 깨는 것을 피할 수 없습니다 ...
sleske

일반적으로 의존성 주입 이전에 메소드에서 사용하고 있던 것을 기본 매개 변수로 사용합니다. 이상적으로는 클래스의 동작이 변경되지 않으므로 새 생성자 추가를 깨끗한 리팩토링으로 만들 수 있습니다.
Doctor Blue

1
데이터베이스 연결과 같은 리소스 관리 종속성에 대해 설명합니다. 필자의 경우 문제는 종속성을 추가하는 클래스에 여러 하위 클래스가 있다는 것입니다. 컨테이너가 속성을 설정하는 IOC 컨테이너 세계에서 setter를 사용하면 모든 하위 클래스 간의 생성자 인터페이스 복제에 대한 압력이 최소한 완화됩니다.
Niall Connaughton

17

물론 생성자를 착용하면 한 번에 모두 확인할 수 있습니다. 사물을 읽기 전용 필드에 할당하면 생성 시점부터 개체의 종속성에 대해 약간의 보증이 제공됩니다.

새로운 의존성을 추가하는 것은 큰 고통이지만 적어도 이런 식으로 컴파일러는 정확해질 때까지 계속 불평합니다. 좋은 것 같아요.


이것에 대한 하나 더하기. 또한 이것은 순환 종속성의 위험을 극도로 줄입니다 ...
gilgamash

11

많은 수의 선택적 종속성 (이미 냄새 임)이있는 경우 아마도 setter injection이 좋습니다. 생성자 주입은 의존성을 더 잘 드러냅니다.


11

일반적으로 선호되는 방법은 생성자 주입을 가능한 많이 사용하는 것입니다.

생성자 주입은 객체가 올바르게 작동하기 위해 필요한 종속성이 무엇인지 정확하게 나타냅니다. 일부 종속성이 설정되지 않았기 때문에 객체를 새로 고치고 메소드를 호출 할 때 충돌하는 것보다 더 성가신 것은 없습니다. 생성자가 반환 한 객체는 작동 상태에 있어야합니다.

생성자를 하나만 사용하면 디자인이 단순하고 모호함을 피할 수 있습니다 (인간이 아닌 경우 DI 컨테이너의 경우).

Mark Seemann 이 자신의 저서 ".NET에 의존성 주입 (Dependency Injection in .NET)"에서 로컬 기본값 을 호출 할 때 특성 주입을 사용할 수 있습니다 . 종속성은 선택 사항입니다. 제대로 작동하는 구현을 제공 할 수 있지만 호출자가 다른 경우를 지정할 수 있기를 바랍니다. 필요합니다.

(아래의 이전 답변)


주입이 필수 인 경우 생성자 주입이 더 좋다고 생각합니다. 생성자가 너무 많으면 생성자 대신 팩토리를 사용하는 것이 좋습니다.

주입이 선택 사항이거나 반쯤 통과 시키려면 세터 주입이 좋습니다. 나는 일반적으로 세터를 좋아하지 않지만 맛의 문제입니다.


나는 일반적으로 주입을 반쯤 변경하는 것이 나쁜 스타일이라고 주장합니다 (개체에 숨겨진 상태를 추가하기 때문에). 그러나 물론 예외없이 규칙은 없습니다 ...
sleske

그렇기 때문에 내가 setter를 너무 좋아하지 않는다고 말한 이유는 ... 생성자 접근 방식을 좋아하므로 변경할 수 없습니다.
Philippe

"이로 인해 생성자가 너무 많으면 생성자 대신 팩토리를 사용하는 것이 좋습니다." 기본적으로 런타임 예외를 지연시키고 문제가 발생하여 서비스 로케이터 구현에 멈출 수도 있습니다.
MeTitus

@Marco는 내 전 대답이며 당신은 옳습니다. 생성자가 많으면 클래스가 너무 많은 일을한다고 주장합니다 :-) 또는 추상 팩토리를 고려하십시오.
Philippe

7

그것은 개인적인 취향의 문제입니다. 개인적으로 나는 setter 주입을 선호하는 경향이 있습니다. 왜냐하면 런타임에 구현을 대체 할 수있는 방식으로 더 많은 유연성을 제공하기 때문입니다. 또한 많은 인수를 가진 생성자는 내 의견으로는 깨끗하지 않으며 생성자에서 제공되는 인수는 비 선택적 인수로 제한되어야합니다.

클래스 인터페이스 (API)가 작업을 수행하는 데 필요한 것이 명확하다면, 좋습니다.


다운 보팅 이유를 지정해주세요.
nkr1pt

3
예, 인수가 많은 생성자는 나쁩니다. 그래서 많은 생성자 매개 변수를 사용하여 클래스를 리팩터링하는 이유는 :-)입니다.
sleske

10
@ nkr1pt : 대부분의 사람들 (포함)은 주입이 수행되지 않으면 런타임에 실패하는 클래스를 만들 수 있으면 setter 주입이 나쁘다는 데 동의합니다. 그러므로 누군가 당신의 개인적인 취향에 대한 당신의 진술에 반대한다고 생각합니다.
sleske

7

나는 개인적으로 생성자에 의존성을 주입 하는 것보다 추출 및 재정의 "패턴"을 선호합니다 . 주로 귀하의 질문에 요약 된 이유 때문입니다. 속성을로 설정 virtual한 다음 파생 된 테스트 가능 클래스에서 구현을 재정의 할 수 있습니다 .


1
패턴의 공식 이름은 "템플릿 방법"이라고 생각합니다.
davidjnelson

6

클래스의 종속성 요구 사항을 "강화"하는 데 도움이되므로 생성자 삽입을 선호합니다. 그것이 c'tor에 있다면 소비자 앱이 컴파일되도록 객체를 설정해야합니다. setter injection을 사용하면 런타임까지 문제가 있음을 알지 못할 수 있으며 개체에 따라 런타임에 늦을 수 있습니다.

주입 된 객체가 초기화와 같은 많은 작업 자체를 필요로 할 때 때때로 setter injection을 사용합니다.


6

이것이 가장 논리적으로 보이기 때문에 생성자 주입을 사용합니다. 그것의 내 수업을 말하는 등 필요로 그 일을하기 위해 이러한 종속성을. 선택적인 의존성이라면 속성이 합리적으로 보입니다.

또한 컨테이너를 사용하여 만든 발표자의 ASP.NET View와 같은 컨테이너에 참조가없는 것을 설정하기 위해 속성 삽입을 사용합니다.

나는 그것이 캡슐화를 깨뜨릴 것이라고 생각하지 않는다. 내부 작업은 내부적으로 유지되어야하며 종속성은 다른 문제를 처리합니다.


답변 주셔서 감사합니다. 확실히 생성자가 인기있는 답변 인 것 같습니다. 그러나 나는 그것이 어떤 방식으로 캡슐화를 깨뜨린다고 생각합니다. 의존성 주입 전, 클래스는 업무 수행에 필요한 구체적인 유형을 선언하고 인스턴스화합니다. DI를 사용하면 서브 클래스 (및 모든 수동 인스턴스 화기)가 기본 클래스가 사용하는 도구를 알 수 있습니다. 새 종속성을 추가하면 이제 종속 자체를 사용할 필요가없는 경우에도 모든 하위 클래스에서 인스턴스를 연결해야합니다.
Niall Connaughton

좋은 긴 답변을 작성 하고이 사이트의 예외로 인해 잃어 버렸습니다! :( 요약하면 기본 클래스는 일반적으로 논리를 재사용하는 데 사용됩니다.이 논리는 하위 클래스로 쉽게 이동할 수 있습니다. 따라서 여러 외부 개체에 의존하는 기본 클래스와 하위 클래스 = 하나의 관심사를 생각할 수 있습니다. 의존성이 있다고해서 이전에 비공개로 유지 한 모든 것을 공개 할 필요는 없습니다.
David Kiff

3

고려해야 할 한 가지 옵션은 간단한 단일 종속성으로 복잡한 다중 종속성을 구성하는 것입니다. 즉, 복합 종속성에 대한 추가 클래스를 정의하십시오. 이렇게하면 WRT 생성자 주입이 훨씬 쉬워집니다 (호출 당 더 적은 매개 변수).

물론 어떤 종류의 의존성에 대한 논리적 그룹이있는 경우 가장 합리적이므로 화합물은 임의의 집합 이상이며 단일 화합물 의존성에 대해 여러 개의 의존성이있는 경우 가장 적합합니다.하지만 매개 변수 블록 "pattern"은 오랫동안 주변에 있었고, 내가 본 대부분은 꽤 임의적이었습니다.

하지만 개인적으로 저는 메소드 / 프로퍼티 세터를 사용하여 의존성, 옵션 등을 지정하는 것을 좋아합니다. 통화 이름은 진행 상황을 설명하는 데 도움이됩니다. 그러나이 방법은 설정하는 스 니펫 예제를 제공하고 종속 클래스가 충분한 오류 검사를 수행하는지 확인하는 것이 좋습니다. 설정에 유한 상태 모델을 사용할 수 있습니다.


3

최근 에 클래스에 여러 종속성이 있는 상황이 발생했지만 각 구현에서 종속성 중 하나만 변경되어야합니다. 데이터 액세스 및 오류 로깅 종속성은 테스트 목적으로 만 변경 될 수 있으므로 선택적 매개 변수를 추가했습니다. 해당 종속성에 대한 를 하고 생성자 코드에서 해당 종속성의 기본 구현을 제공했습니다. 이런 식으로 클래스는 소비자가 클래스를 재정의하지 않는 한 기본 동작을 유지합니다.

선택적 매개 변수 사용은 .NET 4와 같이 매개 변수를 지원하는 프레임 워크에서만 수행 할 수 있습니다 (VB.NET에는 항상 있었지만 C # 및 VB.NET 용). 물론, 클래스의 소비자가 재 할당 할 수있는 속성을 사용하여 유사한 기능을 수행 할 수 있지만, 개인 인터페이스 객체를 생성자의 매개 변수에 할당하면 불변성의 이점을 얻을 수 없습니다.

모든 소비자가 제공해야하는 새로운 종속성을 도입하는 경우 생성자와 클래스를 소비자로 사용하는 모든 코드를 리팩터링해야합니다. 위의 제안은 실제로 모든 현재 코드에 기본 구현을 제공 할 수 있지만 필요한 경우 기본 구현을 무시할 수있는 기능을 제공하는 경우에만 적용됩니다.


1

이것은 오래된 게시물이지만 나중에 필요할 경우 유용합니다.

https://github.com/omegamit6zeichen/prinject

나는 비슷한 생각을 가지고이 프레임 워크를 생각해 냈습니다. 아마도 완전하지는 않지만 속성 삽입에 중점을 둔 프레임 워크의 아이디어입니다.


0

구현 방법에 따라 다릅니다. 구현에 들어가는 값이 자주 바뀌지 않는다고 느낄 때마다 생성자 주입을 선호합니다. 예 : compag 전략이 오라클 서버와 함께 진행되는 경우, 생성자 주입을 통해 bean achiveing ​​연결에 대한 내 데이터 소스 값을 구성합니다. 그렇지 않으면 내 앱이 제품이고 고객의 모든 DB에 연결할 수있는 경우 setter injection을 통해 이러한 DB 구성 및 다중 브랜드 구현을 구현합니다. 방금 예를 들었지만 위에서 언급 한 시나리오를 구현하는 더 좋은 방법이 있습니다.


1
사내에서 코딩 할 때도 항상 재배포 가능 코드를 개발하는 독립 계약자라는 관점에서 코딩합니다. 나는 그것이 오픈 소스가 될 것이라고 가정합니다. 이런 식으로 코드가 모듈 식이고 플러그 가능하며 SOLID 원칙을 따르는 지 확인합니다.
Fred

0

생성자 주입은 의존성을 명시 적으로 보여 주므로 생성자에서 인수를 확인하면 코드를 더 읽기 쉽고 처리 할 수없는 런타임 오류가 덜 발생하지만 실제로는 개인적인 견해로 귀결되며 DI를 많이 사용할수록 더 많이 사용할 수 있습니다 프로젝트에 따라 어떤 방향 으로든 앞뒤로 흔들리는 경향이 있습니다. 개인적으로 긴 인수 목록을 가진 생성자와 같은 코드 냄새 문제가 있으며, 어쨌든 객체 소비자는 객체를 사용하기 위해 종속성을 알아야한다고 생각하므로 속성 삽입을 사용하는 경우가됩니다. 나는 속성 주입의 내재적 특성을 좋아하지 않지만 더 우아하게 보이고 코드를 더 깨끗하게 만듭니다. 다른 한편으로, 생성자 주입은 더 높은 수준의 캡슐화를 제공합니다.

특정 시나리오에 따라 생성자 또는 속성별로 주입을 현명하게 선택하십시오. DI가 필요한 것처럼 보이고 DI가 나쁜 디자인 및 코드 냄새를 방지 할 수 있다고 생각할 필요는 없습니다. 노력과 복잡성이 이점보다 큰 경우 패턴을 사용하려는 노력이 가치가없는 경우가 있습니다. 간단하게 유지하십시오.

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