의존성 주입; 상용구 코드를 줄이기위한 모범 사례


25

나는 간단한 질문을 가지고 있으며 답이 확실하지 않지만 시도해 봅시다. C ++로 코딩하고 전역 상태를 피하기 위해 의존성 주입을 사용하고 있습니다. 이것은 꽤 잘 작동하며 예기치 않은 / 정의되지 않은 동작으로 자주 실행되지 않습니다.

그러나 프로젝트가 성장함에 따라 상용구로 간주되는 많은 코드를 작성하고 있음을 알고 있습니다. 최악의 경우 : 실제 코드보다 상용구 코드가 더 많기 때문에 때로는 이해하기가 어렵습니다.

좋은 예를 능가하는 것은 없습니다.

Time 객체를 만드는 TimeFactory라는 클래스가 있습니다.

자세한 내용은 (관련성이 확실하지 않음) : Time 객체는 시간이 다른 형식을 가질 수 있고 이들 사이의 변환이 선형 적이지도 간단하지 않기 때문에 상당히 복잡합니다. 각 "시간"에는 변환을 처리하고 동일하고 올바르게 초기화 된 동기화 프로그램이 있는지 확인하기 위해 동기화 도구가 포함되어 있습니다. TimeFactory를 사용합니다. TimeFactory에는 인스턴스가 하나 뿐이며 응용 프로그램 전체에 적용되므로 싱글 톤을 사용할 수 있지만 변경 가능하므로 싱글 톤으로 만들고 싶지 않습니다.

내 응용 프로그램에서 많은 클래스가 Time 객체를 만들어야합니다. 때때로 그 클래스는 깊이 중첩되어 있습니다.

클래스 B의 인스턴스 등을 포함하는 클래스 A가 클래스 D까지 있다고 가정 해 봅시다. 클래스 D는 Time 객체를 만들어야합니다.

순진한 구현에서 TimeFactory를 클래스 A의 생성자에 전달하고 클래스 A의 생성자에 클래스 D까지 전달합니다.

이제 TimeFactory와 같은 클래스와 위의 클래스와 같은 클래스 계층이 있다고 가정합니다. 의존성 주입을 사용하려고하는 모든 유연성과 가독성을 잃어 버립니다.

내 앱에 주요 디자인 결함이 없는지 궁금해지기 시작했습니다 ... 아니면 의존성 주입을 사용하는 데 필요한 악이 있습니까?

어떻게 생각해 ?


5
내가 이해했듯이 프로그래머는 디자인 관련 질문에 더 많고 스택 오버플로는 "컴파일러 앞에있을 때"-질문에 대한 것이기 때문에 stackoverflow 대신 프로그래머 에게이 질문을 게시했습니다. 내가 틀렸다면 미리 죄송합니다.
Dinaiz

2
관점 지향 프로그래밍,이 작업을 위해 좋다 - en.wikipedia.org/wiki/Aspect-oriented_programming
EL Yusubov

클래스 A, 클래스 B, C 및 D에 대한 팩토리입니까? 그렇지 않은 경우 왜 인스턴스를 생성합니까? 클래스 D에만 Time-object가 필요한 경우 TimeFactory를 사용하여 다른 클래스를 주입하는 이유는 무엇입니까? 클래스 D에는 실제로 TimeFactory가 필요합니까, 아니면 Time 인스턴스 만 주입 될 수 있습니까?
simoraman

D는 D 만 가지고있는 정보로 Time 객체를 만들어야합니다. 나머지 의견에 대해 생각해야합니다. 내 코드에서 "누가 사람을 만드는지"에 문제가있을 수 있습니다.
Dinaiz

"누가 만드는 사람"은 IoC 컨테이너로 해결됩니다. 자세한 내용은 제 답변을 참조하십시오. "누가 만드는 사람"은 Dependency Injection을 사용할 때 물어볼 수있는 가장 좋은 질문 중 하나입니다.
게임 개발자

답변:


23

내 응용 프로그램에서 많은 클래스가 Time 객체를 만들어야합니다.

Time클래스가 응용 프로그램의 "일반 인프라"에 속하는 매우 기본적인 데이터 유형 인 것 같습니다 . DI는 그러한 수업에 적합하지 않습니다. string문자열을 사용하는 코드의 모든 부분에 같은 클래스 를 주입해야하고 stringFactory새로운 문자열을 만들 수있는 유일한 가능성으로 사용해야하는 경우의 의미에 대해 생각 하십시오. 프로그램의 가독성은 크기.

그래서 내 제안 : 같은 일반 데이터 유형에 DI를 사용하지 마십시오 Time. 쓰기 단위에 대한 테스트 Time는 완료되면 클래스 자체, 그리고, 그냥 같은 프로그램에서 모든 곳에서 사용할 string클래스, 또는 vector클래스 또는 표준 LIB의 다른 클래스. 실제로 서로 분리해야하는 구성 요소에는 DI를 사용하십시오.


당신은 그것을 완전히 맞았습니다. "프로그램의 가독성은 몇 배나 줄어 듭니다." : 그것은 정확히 그렇습니다. 여전히 공장을 사용하고 싶다면 싱글 톤으로 만드는 것이 나쁘지 않습니까?
Dinaiz

3
@ Dinaiz : 아이디어는 Time프로그램의 다른 부분과 다른 부분 사이의 긴밀한 결합을 받아들이는 것입니다 . 그래서 당신은 또한 엄격한 결합을 받아 들일 수 있습니다 TimeFactory. 그러나 피해야 할 TimeFactory것은 상태 가있는 단일 전역 객체 (예 : 로캘 정보 또는 이와 유사한 것)를 사용하는 것입니다. 이로 인해 프로그램에 심한 부작용이 발생하고 일반적인 테스트와 재사용이 매우 어려워 질 수 있습니다. 스테이트리스로 만들거나 싱글 톤으로 사용하지 마십시오.
Doc Brown

실제로는 상태를 가져야하므로 "단일 톤으로 사용하지 마십시오"라고 말하면 "종속성 주입을 사용하여 인스턴스를 필요한 모든 오브젝트에 인스턴스를 전달하십시오"라는 의미입니다.
Dinaiz

1
@Dinaiz : 정직, 의존 - 사용하여 프로그램의 일부의 종류와 수에, 국가의 종류에 TimeTimeFactory는 관련 향후 확장에 필요한 "진화 능력 (evolvability)"의 정도에, TimeFactory등.
Doc Brown

OK 나는 의존성 주입을 유지하려고 노력하지만 더 많은 상용구가 필요없는 더 영리한 디자인을 가지고 있습니다! 많은 문서 감사합니다 '
Dinaiz

4

"종속성 주입을 사용하려고하는 모든 유연성과 가독성을 잃어 버렸습니다"라는 말의 의미는 무엇입니까?-DI는 가독성에 관한 것이 아닙니다. 객체 간의 종속성을 분리하는 것입니다.

클래스 A를 만드는 클래스 B, 클래스 B를 만드는 클래스 C 및 클래스 C를 만드는 클래스 D가있는 것 같습니다.

클래스 B에 클래스 B를 주입하십시오. 클래스 C에 클래스 C를 주입하십시오. 클래스 C에 클래스 D를 주입하십시오.


DI는 가독성에 관한 것일 수 있습니다. 메쏘드 중간에 "매직 적으로"전역 변수가 나타나는 경우, 유지 관리 관점에서 코드를 이해하는 데 도움이되지 않습니다. 이 변수는 어디에서 왔습니까? 누가 그것을 소유합니까? 누가 초기화합니까? 그리고 등등 ... 당신의 두 번째 요점은 아마도 맞지 만 제 경우에는 적용되지 않는다고 생각합니다. 답장을 보내 주셔서 감사합니다
Dinaiz

DI는 "신규 / 삭제"가 코드에서 마술처럼 제거되므로 가독성이 향상됩니다. 동적 할당 코드에 대해주의 할 필요가없는 경우 읽기 쉽습니다.
게임 개발자

또한 의존성이 만들어지는 방법에 대해 더 이상 가정을 할 수 없기 때문에 코드가 조금 더 강해 진다고 말하는 것도 잊어라. 그리고 그것은 수업을 더 쉽게 관리 할 수있게 해줍니다. 내 대답보기
GameDeveloper

3

왜 타임 팩토리를 싱글 톤으로 만들고 싶지 않은지 잘 모르겠습니다. 전체 앱에 인스턴스가 하나만있는 경우 실제로 단일 항목입니다.

즉, 동기화 블록으로 올바르게 보호되는 경우를 제외하고는 변경 가능한 객체를 공유하는 것이 매우 위험합니다.이 경우 싱글 톤이 아닌 이유가 없습니다.

의존성 주입을 원한다면, 스프링 또는 다른 의존성 주입 프레임 워크를보고 주석을 사용하여 매개 변수를 자동으로 지정할 수 있습니다.


Spring은 Java 용이며 C ++을 사용합니다. 그런데 왜 2 명이 당신을 공감 했습니까? 귀하의 게시물에 동의하지 않지만 여전히 포인트가 있습니다 ...
Dinaiz

나는 downvote가 Spring에 대한 언급뿐만 아니라 그 자체로 질문에 대답하지 않았기 때문이라고 가정합니다. 그러나 Singleton 사용에 대한 제안은 제 생각에는 유효하며 질문에 대답하지 못할 수도 있지만 유효한 대안을 제안하고 흥미로운 디자인 문제가 발생합니다. 이런 이유로 나는 +1했습니다.
Fergus In London

1

이는 Doc Brown에 대한 보완 적 답변이자 여전히 질문과 관련된 Dinaiz의 답변되지 않은 의견에 답변하는 것을 목표로합니다.

아마도 필요한 것은 DI를 수행하기위한 프레임 워크입니다. 복잡한 계층 구조가 반드시 나쁜 설계를 의미하는 것은 아니지만 D에 직접 주입하는 대신 TimeFactory 상향식 (A에서 D로)을 주입해야하는 경우 Dependency Injection을 수행하는 방식에 문제가있을 수 있습니다.

싱글 톤? 괜찮습니다. Instanceor ++와 같은 DI에 IoC 컨테이너를 사용하면 TimeFactory를 단일 istance로 바인딩하기 만하면됩니다.이 예제는 C ++ 11이지만 C ++입니다. C ++ 11을 이미 사용하고 계십니까?

Infector::Container ioc; //your app's context

ioc.bindSingleAsNothing<TimeFactory>(); //declare TimeFactory to be shared
ioc.wire<TimeFactory>(); //wire its constructor 

// if you want to be sure TimeFactory is created at startup just request it
// (else it will be created lazily only when needed)
auto myTimeFactory = ioc.buildSingle<TimeFactory>();

이제 IoC 컨테이너의 장점은 시간 팩토리를 D까지 전달할 필요가 없다는 것입니다. 클래스 "D"에 타임 팩토리가 필요한 경우, 시간 팩토리를 클래스 D의 생성자 매개 변수로 두십시오.

ioc.bindAsNothing<A>(); //declare class A
ioc.bindAsNothing<B>(); //declare class B
ioc.bindAsNothing<D>(); //declare class D

//constructors setup
ioc.wire<D, TimeFactory>(); //time factory injected to class D
ioc.wire<B, D>(); //class D injected to class B
ioc.wire<A, B>(); //class B injected to class A

보시다시피 TimeFactory를 한 번만 주입하십시오. "A"를 사용하는 방법? 매우 간단합니다. 모든 수업은 기본으로 주입되거나 공장에서 생산됩니다.

auto myA1 = ioc.build<A>(); //A is not "single" so many different istances
auto myA2 = ioc.build<A>(); //can live at same time

클래스 A를 만들 때마다 최대 D까지의 모든 종속성으로 자동 (지연 한 istantiation) 주입되고 D는 TimeFactory로 주입되므로 1 개의 메소드 만 호출하면 완전한 계층 구조가 준비됩니다 (심지어 복잡한 계층 구조도이 방법으로 해결됩니다) 보일러 플레이트 코드를 많이 제거)

D는 D 만 가질 수있는 정보로 Time 객체를 만들 수 있습니다

TimeFactory에 "create"메소드가 있고 다른 서명 "create (params)"를 사용하면됩니다. 종속성이없는 매개 변수는 종종이 방법으로 해결됩니다. 이것은 또한 보일러 플레이트를 추가하기 때문에 "문자열"또는 "정수"와 같은 것을 주입 할 의무를 제거합니다.

누가 누구를 만들어? IoC 컨테이너는 인스턴스와 팩토리를 생성하고 팩토리는 나머지를 생성합니다 (공장은 임의의 매개 변수로 다른 객체를 생성 할 수 있으므로 팩토리의 상태가 실제로 필요하지 않음). 팩토리를 IoC 컨테이너의 래퍼로 계속 사용할 수 있습니다. 일반적으로 IoC 컨테이너의 주입은 매우 나쁘고 서비스 로케이터를 사용하는 것과 같습니다. 일부 사람들은 IoC 컨테이너를 팩토리로 감싸서 문제를 해결했습니다 (이것은 꼭 필요한 것은 아니지만 컨테이너가 계층 구조를 해결하고 모든 팩토리를 유지 관리하기가 더 쉽다는 장점이 있습니다).

//factory method
std::unique_ptr<myType> create(params){
    auto istance = ioc->build<myType>(); //this code's agnostic to "myType" hierarchy
    istance->setParams(params); //the customization you needed
    return std::move(istance); 
}

또한 의존성 주입을 남용하지 말고 간단한 유형은 클래스의 멤버 또는 지역 범위 변수 일 수 있습니다. 이것은 명백해 보이지만 사람들이 그것을 허용 한 DI 프레임 워크가 있기 때문에 "std :: vector"를 주입하는 것을 보았다. Demeter의 법칙을 항상 기억하십시오 : "실제로 주입해야하는 것만 주입하십시오"


와우 나는 당신의 대답을 전에 보지 못했습니다, 죄송합니다! 매우 유익한 선생님! 공감
Dinaiz

0

A, B 및 C 클래스도 Time 인스턴스를 작성하거나 D 클래스 만 작성해야합니까? 클래스 D 인 경우 A와 B는 TimeFactory에 대해 아무 것도 알아야합니다. C 클래스 내에 TimeFactory의 인스턴스를 생성하고이를 클래스 D에 전달합니다. "인스턴스 생성"이 반드시 C 클래스가 TimeFactory를 인스턴스화해야한다는 것을 의미하지는 않습니다. 클래스 B에서 DClassFactory를 수신 할 수 있으며 DClassFactory는 Time 인스턴스를 작성하는 방법을 알고 있습니다.

DI 프레임 워크가 없을 때 자주 사용하는 기술은 두 가지 생성자를 제공하는 것입니다. 하나는 팩토리를 허용하고 다른 하나는 기본 팩토리를 생성합니다. 두 번째는 일반적으로 보호 / 패키지 액세스를 가지며 주로 단위 테스트에 사용됩니다.


-1

- 최근 향상을 위해 제안 된 또 다른 C ++ 의존성 주입 프레임 워크 구현 https://github.com/krzysztof-jusiak/di - 라이브러리 매크로입니다 이하 (무료), 헤더 만, C ++ 03 / C ++ (11) 형식 안전, 컴파일 시간, 매크로없는 생성자 종속성 주입을 제공하는 / C ++ 14 라이브러리.


3
P.SE에 대한 오래된 질문에 대답하는 것은 프로젝트를 광고하는 나쁜 방법입니다. 어떤 질문과 관련이있을 수있는 수만 개의 프로젝트가 있다는 것을 고려하십시오. 모든 저자가 여기에 게시하면 사이트의 응답 품질이 떨어질 것입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.