2009-10-01 8 views
136

나는 클래스를 리팩터링하고 그것에 새로운 의존성을 추가하고있다. 클래스는 현재 생성자에서 기존 종속성을 사용하고 있습니다. 일관성을 위해 매개 변수를 생성자에 추가합니다.
물론 유닛 테스트를위한 몇 가지 하위 클래스가 있습니다. 이제는 모든 생성자를 일치 시키려고하는 게임을하고 있습니다. 연령대가 다르게 보입니다.
setter에서 속성을 사용하는 것이 종속성을 얻는 더 좋은 방법이라고 생각합니다. 삽입 된 종속성은 클래스의 인스턴스를 구성하기위한 인터페이스의 일부 여야한다고 생각하지 않습니다. 의존성을 추가하면 이제는 모든 사용자 (하위 클래스와 직접 인스턴스를 생성 한 모든 사용자)가 갑자기 그것을 알 수 있습니다. 그것은 캡슐화의 휴식과 같은 느낌.생성자 또는 속성 설정자를 통한 종속성 주입?

여기는 기존 코드의 패턴이 아닌 것 같아요. 그래서 나는 일반적인 합의가 무엇인지, 생성자와 프로퍼티의 장단점에 대해 알아 보려고합니다. 속성 설정자를 사용하고 있습니까?

답변

117

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

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

클래스가 종속성없이 작동 할 수 있으면 설정자는 괜찮습니다.

+9

Null Object 패턴을 사용하고 생성자에 대한 참조가 필요하므로 많은 경우에 적합하다고 생각합니다. 이렇게하면 모든 null 검사와 증가 된 순환 복잡성을 피할 수 있습니다. –

+3

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

+0

의존성이 작동하는 데는 언제 필요하지만 그 의존성의 기본 주입으로 충분합니다. 그러면 종속성이 속성이나 생성자 오버로드에 의해 "대체 가능"해야합니까? –

6

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

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

+0

downvoting하는 이유를 구체적으로 설명하십시오. – nkr1pt

+3

예, 인수가 많은 생성자가 잘못되었습니다. 그래서 많은 생성자 매개 변수가있는 클래스를 리팩터링합니다 :-). – sleske

+10

@ nkr1pt : 주입이 완료되지 않은 경우 런타임에 실패하는 클래스를 생성 할 수 있다면 대부분의 사람 (필자 포함)은 setter 주입이 좋지 않다는데 동의합니다. 나는 누군가가 개인적인 취향이라는 당신의 진술에 반대한다고 믿습니다. – sleske

6

클래스의 종속성 요구 사항을 "시행"하는 데 도움이되므로 생성자 삽입을 선호합니다. 그것이 존재하는 경우 소비자 을 사용하여 개체를 설정하여 앱을 컴파일합니다. setter 주입을 사용하면 런타임까지 문제가 있다는 것을 모를 수 있습니다. 객체에 따라 런타임에 늦을 수도 있습니다.

나는 주입 된 객체가 초기화와 같이 일련의 작업 자체를 필요로하는 경우가 종종있다.

11

많은 종류의 옵션 종속성 (이미 냄새가 있음)이있는 경우 setter injection을 사용하는 것이 좋습니다. 생성자 주입은 의존성을 더 잘 보여줍니다.

17

물론 생성자를 사용하면 모든 것을 한 번에 확인할 수 있습니다. 읽기 전용 필드에 항목을 할당하면 작성 시간과 관련하여 개체의 종속성에 대한 보장이 제공됩니다.

새로운 의존성을 추가하는 것은 정말 고통 스럽지만 최소한 컴파일러는 그것이 올 때까지 불평을 계속합니다. 어느 것이 좋은 것 같아요.

+0

플러스 하나 이것에 더하여 순환 의존성의 위험성을 극도로 줄여줍니다 ... – gilgamash

6

이것은 가장 논리적 인 것처럼 보입니다. 그 클래스가 내 클래스 을 말하는 것은 이러한 의존성이 필요합니다. 선택적인 종속성이 있다면 속성이 합리적인 것처럼 보입니다.

컨테이너를 사용하여 만든 발표자의 ASP.NET보기와 같은 참조가없는 항목을 설정하는데도 속성 주입을 사용합니다.

캡슐화가 중단되었다고 생각하지 않습니다. 내부 작업은 내부에 있어야하며 종속성은 다른 관심사를 처리합니다.

+0

답을 주셔서 감사합니다. 생성자가 대중적인 대답 인 것 같습니다 확실히 그러나 나는 그것이 어떤 식 으로든 캡슐화를 중단한다고 생각합니다. DI를 사용하면 하위 클래스 (및 수동 인스턴스 생성자)가 기본 클래스에서 사용하는 도구를 알 수 있습니다. 새 종속성을 추가하고 이제는 체인을 사용해야합니다. 종속성 자체를 사용할 필요가없는 경우에도 모든 하위 클래스의 인스턴스가 적용됩니다. –

+0

멋진 long을 작성했습니다. 이 사이트에서 예외로 인해 답변을 잃어 버렸습니다! :(요약하면 baseclass는 일반적으로 로직을 재사용하는 데 사용됩니다.이 논리는 서브 클래스로 쉽게 갈 수 있습니다 ... 그래서 당신은 baseclass와 subclass = one 관심사를 생각할 수 있습니다. 이것은 여러 외부 객체에 의존합니다. 당신이 의존성이 있다는 사실은 이전에 비밀로 유지했을만한 것을 드러 낼 필요가 있다는 것을 의미하지는 않습니다. –

7

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

+1

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

11

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

생성자 삽입은 개체가 올바르게 작동하기 위해 필요한 종속성이 무엇인지 정확하게 설명합니다. 개체를 새로 작성하는 것보다 성가신 것이고 종속성이 설정되지 않았기 때문에 개체를 호출 할 때 충돌이 발생하는 것은 없습니다. 생성자가 반환 한 객체는 작동 상태 여야합니다.

단 하나의 생성자 만 있으면 디자인이 단순 해지고 모호성을 피할 수 있습니다 (사람이 아닌 경우 DI 컨테이너 용). 당신이 마크 시만은 ".NET에서 의존성 주입"그의 책에 로컬 기본을 부르는있을 때 당신은 속성 주입을 사용할 수 있습니다

: 당신은 좋은 작업 구현을 제공하지만, 호출자를 허용 할 수 있기 때문에 종속성은 선택 사항입니다 필요한 경우 다른 것을 지정하십시오.

(아래 전 답)


나는 주사가 필수 인 경우 그 생성자 주입 더 나은 생각합니다. 이것이 너무 많은 생성자를 추가하면 생성자 대신에 팩토리를 사용하는 것을 고려하십시오.

주사기가 선택 사항이거나 설정을 반으로 변경하려는 경우 세터 주입이 효과적입니다. 나는 일반적으로 세터를 좋아하지 않지만 맛은 문제 다.

+0

나는 보통 주사를 반기 (당신이 당신의 객체에 숨겨진 상태를 추가하고 있기 때문에) 나쁜 스타일입니다. 그러나 물론 예외는 없습니다. – sleske

+0

그래, 내가 setter를 너무 좋아하지 않는다고 말한 이유는 ... 나는 생성자를 좋아한다. 접근 방식을 바꿀 수 없다. " – Philippe

+0

"이렇게하면 생성자가 너무 많아서 생성자 대신에 팩토리를 사용하는 것이 좋습니다. "기본적으로 런타임 예외를 연기하고 상황이 잘못되어 끝날 수도 있습니다 서비스 로케이터 구현에 방해가되었습니다. – Marco

19

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

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

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. 
    } 
} 

종속성 주입 지점 중 하나는 클래스의 종속성을 나타내는 것입니다. 클래스에 너무 많은 의존성이 있다면, 약간의 리팩토링이 일어날 때가있을 것입니다 : 클래스의 모든 메소드가 모든 의존성을 사용합니까? 그렇지 않다면 클래스가 분리 될 수있는 곳을 확인하는 좋은 시작점입니다.

+0

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

+0

일반적으로 의존성 삽입 이전에 메서드에서 사용했던 것을 기본 매개 변수로 사용합니다. 이상적으로는 클래스의 동작이 변경되지 않으므로 새 생성자를 추가하면 클린 리팩토링이 가능합니다. – Scott

+1

데이터베이스 연결과 같은 종속성을 관리하는 리소스에 관해서 제가 말씀 드리고 싶습니다. 내 경우에는 문제가 종속성을 추가하는 클래스에 여러 하위 클래스가 있다고 생각합니다. 속성이 컨테이너에 의해 설정되는 IOC 컨테이너 세계에서 setter를 사용하면 적어도 모든 하위 클래스 간의 생성자 인터페이스 복제에 대한 부담을 덜어줍니다. –

3

간단한 단일 종속성으로 복잡한 다중 종속성을 작성하는 것이 고려해야 할 하나의 옵션이 있습니다. 즉, 복합성 종속성에 대한 추가 클래스를 정의하십시오.이로 인해 WRT 생성자 삽입이 조금 더 쉬워지고 (즉, 호출 당 매개 변수가 줄어들 었음) - 반드시 모든 공급 의존성을 인스턴스화해야합니다.

물론 종속성의 논리적 그룹화가있는 경우 가장 적합합니다. 따라서 화합물은 임의의 집계 이상이며, 단일 화합물 종속성에 대해 여러 종속 요소가있는 경우 가장 적합합니다. 그러나 매개 변수 블록 "패턴"은 오랫동안 주변에 있었고, 제가 본 대부분의 사람들은 꽤 자의적이었습니다.

필자는 개인적으로 의존성, 옵션 등을 지정하기 위해 메소드/속성 설정자를 사용하는 팬입니다. 호출 이름이 무슨 일이 일어나는지 설명하는 데 도움이됩니다. 그렇지만, 어떻게 설정하는 지에 대한 예제 코드를 제공하고 종속 클래스가 충분한 오류 검사를하는지 확인하는 것이 좋습니다. 설정에 유한 상태 모델을 사용할 수 있습니다.

3

나는 최근에 ran into a situation 클래스에 여러 개의 종속성이 있지만 종속성 중 하나만 각 구현에서 반드시 변경 될 예정입니다. 데이터 액세스 및 오류 로깅 종속성은 테스트 목적으로 만 변경 될 가능성이 높기 때문에 이러한 종속성에 선택적 매개 변수 을 추가하고 해당 생성자 코드에서 이러한 종속성의 기본 구현을 제공했습니다. 이런 식으로 클래스는 클래스의 소비자에 의해 무시되지 않는 한 기본 동작을 유지합니다.

선택적 매개 변수를 사용하는 것은 .NET 4 (C# 및 VB.NET의 경우 VB.NET에 항상 포함되어 있지만)와 같이 해당 매개 변수를 지원하는 프레임 워크에서만 수행 할 수 있습니다. 물론 클래스의 소비자가 다시 할당 할 수있는 속성을 사용하여 유사한 기능을 수행 할 수는 있지만 생성자의 매개 변수에 할당 된 개인 인터페이스 개체를 사용하면 제공되는 불변성의 이점을 얻지 못합니다.

이 모든 내용은 모든 소비자가 제공해야하는 새로운 종속성을 도입 할 경우 생성자와 해당 소비자의 모든 코드를 리팩토링해야 할 것입니다. 위의 제안 사항은 모든 현재 코드에 대해 기본 구현을 제공 할 수 있지만 필요에 따라 기본 구현을 재정의 할 수있는 기능을 제공하는 경우에만 실제로 적용됩니다.

0

구현 방법에 따라 다릅니다. 구현에 들어가는 값이 자주 변경되지 않는다고 느낄 때마다 생성자 주입을 선호합니다. 예 : compnay stragtegy가 oracle 서버와 함께 제공되는 경우 생성자 주입을 통해 Bean에 대한 내 datasource 값을 구성합니다. 그렇지 않으면 내 앱이 제품이고 고객의 모든 데이터베이스에 연결할 수있는 기회가되면 설정 기 주입을 통해 이러한 db 구성 및 다중 브랜드 구현을 구현할 것입니다. 방금 예제를 들었지만 위에서 언급 한 시나리오를 구현하는 데 더 좋은 방법이 있습니다.

+1

사내에서 코딩 할 때도 필자는 재배포 가능 코드를 개발하는 독립 계약자임을 항상 염두에두고 있습니다. 나는 그것이 오픈 소스가 될 것이라고 생각한다. 이렇게하면 코드가 모듈화되고 플러그 가능하고 솔리드 원칙을 따르는 것을 확인합니다. – Fred

0

생성자 삽입은 생성자에서 인수가 검사되면 코드가 읽기 쉽고 처리되지 않은 런타임 오류가 발생하지 않도록 종속성을 명시 적으로 나타내지 만 실제로 개인적인 견해로 내려와 DI를 많이 사용할수록 더 많은 경우 프로젝트에 따라 앞뒤로 어느 방향 으로든 움직일 수 있습니다. 저는 개인적으로 인수의 긴 목록을 가진 생성자와 같은 코드 냄새와 관련한 문제를 가지고 있습니다. 어쨌든 객체를 사용하기 위해서는 객체의 소비자가 의존성을 알아야하기 때문에 속성 주입을 사용하는 경우가 있습니다. 나는 속성 주입의 내재적 인 성격을 좋아하지 않지만, 좀 더 우아하고 깨끗한 코드를 발견합니다. 그러나 반면에 생성자 삽입은 더 높은 수준의 캡슐화를 제공하며 내 경험에 따르면 기본 생성자를 피하려고합니다. 조심하지 않으면 캡슐화 된 데이터의 무결성에 나쁜 영향을 줄 수 있습니다.

특정 시나리오에 따라 생성자 또는 속성별로 현명한 주입을 선택하십시오.DI가 필요하다고 느끼기 때문에 DI를 사용해야한다고 생각하지 마십시오. 나쁜 디자인과 코드의 냄새를 예방할 것입니다. 노력과 복잡성이 이점을 능가하는 경우 패턴을 사용하려는 노력은 가치가 없습니다. 단순하게 유지하십시오.

0

이 이전 게시물이지만, 미래에 필요한 경우 어쩌면이 어떤 소용이 :

https://github.com/omegamit6zeichen/prinject

나는 비슷한 생각을했고,이 프레임 워크를 내놓았다. 아마 완벽하지는 않지만 속성 주입에 초점을 둔 프레임 워크에 대한 아이디어입니다