의존성 주입을 사용해야하는 이유는 무엇입니까?


95

종속성 주입을 사용해야하는 이유에 대한 리소스를 찾는 데 어려움을 겪고 있습니다 . 내가 보는 대부분의 리소스는 객체의 인스턴스를 객체의 다른 인스턴스로 전달한다고 설명하지만 왜 그럴까요? 이것은 깨끗한 아키텍처 / 코드만을위한 것입니까 아니면 성능 전체에 영향을 줍니까?

왜 다음을해야합니까?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

다음 대신에?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}

8
deactivateProfile ()에 대한 하드 코딩 된 종속성을 소개합니다 (나쁜). 첫 번째 코드에는 더 많은 코드가 분리되어 있으므로 변경하고 테스트하기가 더 쉽습니다.
Aulis Ronkainen

3
왜 첫 번째를 하시겠습니까? 설정을 전달한 다음 그 값을 무시합니다.
Phil N DeBlanc

57
나는 downvotes에 동의하지 않습니다. 주제가 전문가에게는 사소한 것으로 간주 될 수 있지만, 문제는 장점이있다. 만약 의존성 역전이 사용되어야한다면, 그것을 사용하는 것이 정당화되어야한다.
Flater

13
@PhilNDeBlanc :이 코드는 명확하게 지나치게 단순화되었으며 실제 논리를 나타내는 것이 아닙니다. 그러나 이전 상태에 신경 쓰지 않고 false로 deactivateProfile설정하는 isActive것이 올바른 접근 방법이라고 제안합니다. 메소드를 호출한다는 것은 본질적으로 현재 (비활성) 상태가 아니라 비활성 으로 설정 한다는 의미 입니다.
Flater

2
코드는 의존성 주입 또는 반전의 예가 아닙니다. 파라미터 화의 예입니다 (DI보다 훨씬 낫습니다).
jpmc26

답변:


104

장점은 의존성 주입이 없으면 Profile 클래스라는 것입니다.

  • 설정 개체를 만드는 방법을 알아야합니다 (단일 책임 원칙 위반)
  • 항상 같은 방식으로 설정 개체를 만듭니다 (두 개체간에 밀접한 연결을 만듭니다)

그러나 의존성 주입

  • 설정 개체를 만드는 논리는 다른 곳에 있습니다.
  • 다양한 종류의 설정 개체를 사용하기 쉽습니다.

이 특정 경우에는 관련이 없어 보일 수도 있지만 설정 객체에 대해서는 이야기하지 않고 DataStore 객체는 다른 구현을 가질 수 있습니다. 하나는 파일에 데이터를 저장하고 다른 객체는 데이터를 저장합니다. 데이터베이스. 그리고 자동화 된 테스트를 위해서는 모의 구현도 원합니다. 지금 당신은 정말 프로파일 클래스가 사용하는 하나의 하드 코드 싶지 않아 - 그리고 더 중요한 것은, 당신은 정말, 정말 프로파일 클래스는 파일 시스템 경로, DB 연결 및 암호에 대해 알고 싶어하지 않기 때문에 데이터 저장소 객체의 생성 다른 곳에서 일어날 수 있습니다.


23
This may seem (or even be) irrelevant in this particular case사실 그것은 매우 관련성이 있다고 생각합니다. 어떻게 설정을 하시겠습니까? 필자가 본 많은 시스템에는 하드 코딩 된 기본 설정 세트와 공개 구성이 있으므로 둘 다로드하고 일부 값을 공개 설정으로 덮어 써야합니다. 여러 기본 소스가 필요할 수도 있습니다. 아마도 일부는 디스크에서, 다른 일부는 DB에서 가져 오는 것일 수도 있습니다. 따라서 설정을 가져 오는 데 필요한 전체 논리는 사소하지 않습니다. 코드를 소비하거나 신경 쓰지 않는 것은 아닙니다.
VLAZ

또한 웹 서비스와 같이 사소한 구성 요소에 대한 객체 초기화는 $setting = new Setting();끔찍하게 비효율적 이라고 언급 할 수 있습니다 . 주입 및 객체 인스턴스화가 한 번 발생합니다.
vikingsteve

9
테스트에 모의를 사용하는 것이 더 강조되어야한다고 생각합니다. 코드를 살펴보면 항상 Settings 객체가 될 것이며 변경되지 않으므로 코드를 전달하는 것은 낭비되는 것처럼 보입니다. 그러나 처음으로 솔루션으로 모의 객체를 사용하여 설정 객체를 필요로하지 않고 스스로 프로파일 객체를 테스트하려고 할 때, 그 필요성이 매우 분명합니다.
JPhi1618

2
@ JPhi1618 "DI는 단위 테스트를위한 것"을 강조하는 데있어 문제는 "왜 단위 테스트가 필요한지"라는 질문으로 이어진다는 것입니다. 대답은 당신에게 명백해 보일 수도 있지만, 이점은 분명히 있습니다. 그러나 막 시작한 사람에게는 "이 복잡한 소리를 내기 위해서는이 복잡한 소리를 내야합니다"라는 말은 약간의 경향이 있습니다. 끄기. 따라서 현재 수행중인 작업에 더 적용 할 수있는 다양한 이점을 언급하는 것이 좋습니다.
IMSoP

1
@ user986730-이것은 매우 좋은 지적이며 여기서 실제 원칙은 Dependency Inversion 이라는 점을 강조합니다 .Injection은 단지 하나의 기술입니다. 예를 들어 C에서는 실제로 IOC 라이브러리를 사용하여 종속성을 주입 할 수는 없지만 모의 등을위한 다른 소스 파일 등을 포함시켜 컴파일 타임에 그것들을 뒤집습니다.
Stephen Byrne

64

Dependency Injection을 사용하면 코드를 쉽게 테스트 할 수 있습니다.

Magento의 PayPal 통합에서 잡기 어려운 버그를 수정하는 작업을 할 때 이것을 직접 배웠습니다.

PayPal이 Magento에 실패한 결제에 대해 알리면 문제가 발생합니다. Magento는 실패를 올바르게 등록하지 않습니다.

잠재적 인 수정을 "수동으로"테스트하는 것은 매우 지루한 일입니다. 어떻게 든 "실패한"PayPal 알림을 트리거해야합니다. 전자 수표를 제출하고 취소 한 후 오류가 발생할 때까지 기다려야합니다. 즉, 한 문자 코드 변경을 테스트하는 데 3 일 이상이 소요됩니다.

운 좋게도이 기능을 개발 한 Magento 핵심 개발자는 테스트를 염두에두고 의존성 주입 패턴을 사용하여 사소한 것으로 만듭니다. 이를 통해 다음과 같은 간단한 테스트 사례로 작업을 확인할 수 있습니다.

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

DI 패턴에는 다른 많은 장점이 있다고 확신하지만 테스트 가능성이 높아지면 내 마음에 가장 큰 이점이 있습니다 .

이 문제에 대한 해결책이 궁금하다면 여기에서 GitHub 저장소를 확인하십시오 : https://github.com/bubbleupdev/BUCorefix_Paypalstatus


4
종속성 주입은 하드 코딩 된 종속성이있는 코드보다 코드를 테스트하기가 더 쉽습니다. 비즈니스 로직의 종속성을 완전히 제거하는 것이 훨씬 좋습니다.
앤트 P

1
@AntP가 제안하는 것을 수행하는 한 가지 주요 방법은 매개 변수화 를 이용하는 것 입니다. 데이터베이스에서 나오는 결과를 페이지 템플릿 (일반적으로 "모델"이라고 함)을 채우는 데 사용되는 객체로 변환하는 로직은 페치가 발생하고 있음을 알지 않아야합니다. 해당 객체를 입력으로 가져와야합니다.
jpmc26

4
실제로 @ jpmc26- 기능적 핵심 인 명령형 쉘 에 의존하는 경향이 있습니다. 이는 의존성을 주입하는 대신 도메인에 데이터를 전달하는 멋진 이름입니다. 도메인은 순수하고, 모의없이 단위 테스트를 거친 다음, 지속성, 메시지 전달 등을 조정하는 쉘로 싸여 있습니다.
Ant P

1
테스트 가능성에 중점을 둔 것은 DI 채택에 해롭다 고 생각합니다. 많은 테스트가 필요하지 않거나 이미 테스트를 받고 있다고 생각하는 사람들에게는 호소력이 있습니다. DI없이 깨끗하고 재사용 가능한 코드를 작성하는 것은 사실상 불가능하다고 주장합니다. 테스트는 혜택 목록에서 매우 아래에 있으며 DI의 혜택에 대한 모든 질문에서이 답변의 순위가 1 위 또는 2 위인 것은 실망 스럽습니다.
Carl Leth

26

왜 (문제가 무엇입니까)?

의존성 주입을 사용해야하는 이유는 무엇입니까?

내가 찾은 최고의 니모닉은 " new is glue "입니다. new코드에서 사용할 때마다 해당 코드는 특정 구현에 연결됩니다 . 생성자에서 new를 반복해서 사용하는 경우 특정 구현 체인을 만듭니다 . 또한 클래스를 생성하지 않고 클래스의 인스턴스를 "가져올"수 없으므로 해당 체인을 분리 할 수 ​​없습니다.

예를 들어, 자동차 경주 비디오 게임을 작성한다고 가정하십시오. Game을 만드는 클래스로 시작 하여을 작성하고을 작성하는 RaceTrack8을 Cars작성하십시오 Motor. 이제 Cars다른 가속으로 4 개의 추가를 원한다면을 제외한 언급 된 모든 클래스 를 변경해야합니다 Game.

클리너 코드

깔끔한 아키텍처 / 코드만을위한 것입니까?

.

그러나이 상황에서는 그 방법이 더 잘 설명되어 있기 때문에 명확 하지 않을 수 있습니다 . 실제 장점은 여러 클래스가 관련되어 있고 시연하기 어려운 경우에만 표시되지만 이전 예제에서 DI를 사용했다고 가정 해보십시오. 모든 것을 만드는 코드는 다음과 같습니다.

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

다음 라인을 추가하여 4 가지 자동차를 추가 할 수 있습니다.

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • 아니 변화 RaceTrack, Game, Car, 또는 Motor필요 없었다 - 우리는 우리가 어떤 새로운 버그가 소개하지 않았다는 것을 100 % 확신 할 수 있습니다 의미!
  • 여러 파일 간을 이동하는 대신 화면에서 전체 변경 사항을 동시에 볼 수 있습니다. 이것은 생성 / 설정 / 구성을 자신의 책임 으로 본 결과입니다 . 모터를 만드는 것은 자동차의 일이 아닙니다.

성능 고려 사항

또는 이것이 전체적으로 성능에 영향을 줍니까?

없음 . 그러나 당신에게 완전히 정직하기 위해, 그것은 수도 있습니다.

그러나이 경우에도 걱정할 필요가 없을 정도로 엄청나게 적은 양입니다. 미래의 어떤 시점에서, 당신은 5MHz의 CPU와 2메가바이트 RAM의 상당와 다마고치에 대한 코드를 작성해야한다면, 아마도 당신은 할 수 이것에 대해 걱정해야합니다.

99.999 % *의 경우 버그 수정에 소요되는 시간이 줄어들고 리소스가 많은 알고리즘을 개선하는 데 더 많은 시간이 걸리기 때문에 성능이 향상됩니다.

* 완전히 구성된 수

추가 된 정보 : "하드 코딩 된"

실수하지,이 입니다 매우 여전히 "하드 코딩"- 숫자가 코드에서 직접 작성됩니다. 하드 코딩되지 않은 것은 해당 값을 텍스트 파일 (예 : JSON 형식)에 저장 한 다음 해당 파일에서 값을 읽는 것과 같은 의미입니다.

그러기 위해서는 파일을 읽고 JSON을 파싱하기위한 코드를 추가해야합니다. 예제를 다시 고려하면; 비 DI 버전에서는 a Car또는 a Motor가 파일을 읽어야합니다. 너무 말이되지 않는 것 같습니다.

DI 버전에서는 게임을 설정하는 코드에 추가합니다.


3
하드 코딩 된 광고는 실제로 코드와 구성 파일 사이에 큰 차이가 없습니다. 동적으로 읽는 경우에도 응용 프로그램과 함께 번들로 제공되는 파일은 소스입니다. json 또는 'config'형식의 코드에서 데이터 파일로 값을 가져 오면 값이 사용자가 재정의하거나 환경에 의존하지 않는 한 도움이되지 않습니다.
Jan Hudec

3
나는 실제로 Arduino (16MHz, 2KB)에서 Tamagotchi를 한 번 만들었습니다.
Jungkook

@JanHudec True. 실제로 더 긴 설명이 있었지만 더 짧게 유지하고 DI와의 관련성에 초점을 맞추기 위해 제거하기로 결정했습니다. 100 % 정확하지 않은 것들이 더 있습니다. 전반적으로 답변은 너무 오래 걸리지 않고 DI의 "포인트"를 푸시하도록보다 최적화되었습니다. 달리 말하면, 이것이 DI로 시작할 때 듣고 싶었던 것입니다.
R. Schmitz

17

나는 항상 의존성 주입으로 당황했습니다. 그것은 Java 분야에서만 존재하는 것처럼 보였지만, 그 분야는 그것을 존경합니다. 그것은 혼돈에 질서를 가져 오는 것으로 알려진 위대한 패턴 중 하나였습니다. 그러나 예제는 항상 복잡하고 인공적이어서 문제가 아닌 것을 설정 한 다음 코드를 더 복잡하게 만들어 문제를 해결하려고했습니다.

동료 파이썬 개발자 가이 지혜를 나에게 줄 때 더 이해가되었습니다 : 그것은 함수에 인수를 전달하는 것입니다. 전혀 패턴이 아닙니다. 합리적인 가치를 스스로 제공 할 있었음 에도 불구 하고 논쟁의 여지가있는 것을 요청할 있다는 사실을 상기시키는 것과 같습니다 .

따라서 귀하의 질문은 "왜 내 함수가 인수를 취해야합니까?" 발신자가 의사 결정을 내릴 수 있도록 동일한 답변이 많이 있습니다.

지금 당신이하고 있기 때문에, 물론 비용, 함께 제공 강요 하기 위해 발신자를 일부 (당신이 선택 인수를하지 않는 한) 결정을하고, 인터페이스는 좀 더 복잡하다. 그 대가로 유연성을 얻게됩니다.

따라서이 특정 Setting유형 / 값 을 사용해야하는 이유가 있습니까? 호출 코드가 다른 Setting유형 / 값을 원할만한 이유가 있습니까? ( 테스트는 코드입니다 !)


2
네. IoC는 단순히 "호출자가 결정을 내버려 두는 것"에 관한 것이라는 것을 깨달았을 때 마침내 클릭했습니다. IoC는 구성 요소의 제어가 구성 요소의 작성자에서 구성 요소의 사용자로 이동 함을 의미합니다. 그 시점에서 저는 저보다 똑똑하다고 생각되는 소프트웨어를 이미 충분히 이해하고 있었기 때문에 DI 방식으로 즉시 판매되었습니다.
Joker_vD

1
"그냥 함수에 인수를 전달합니다. 그것은 거의 모든 패턴입니다"이것은 내가 들어 DI의 유일한 좋은 또는 comprehendible 설명입니다 (물론 대부분의 사람들도에 대해 얘기하지 않는 것이 무엇인지 , 오히려 그 혜택). 그런 다음 "제어의 반전"및 "느슨한 커플 링"과 같은 복잡한 전문 용어를 사용하여 실제로 간단한 아이디어인지 이해하기 위해 더 많은 질문이 필요합니다.
애디슨

7

당신이주는 예는 고전적인 의미에서 의존성 주입이 아닙니다. 의존성 주입은 일반적으로 새로 생성 된 오브젝트의 필드에 값을 설정하기 위해 생성자에서 오브젝트를 전달하거나 오브젝트가 작성된 직후 "세터 주입"을 사용하는 것을 말합니다.

예제는 객체를 인스턴스 메소드에 인수로 전달합니다. 그런 다음이 인스턴스 메소드는 해당 오브젝트의 필드를 수정합니다. 의존성 주입? 아니요. 캡슐화와 데이터 숨기기를 깨고 있습니까? 물론!

이제 코드가 다음과 같다면 :

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

그런 다음 의존성 주입을 사용하고 있다고 말하고 싶습니다. 주목할만한 차이점은 Settings생성 자나 객체로 전달되는 Profile객체입니다.

Settings 객체가 비싸거나 구성하기가 복잡하거나 Settings가 런타임 동작을 변경하기 위해 여러 구체적인 구현이 존재하는 인터페이스 또는 추상 클래스 인 경우에 유용합니다.

메소드를 호출하는 대신 설정 오브젝트의 필드에 직접 액세스하므로 종속성 주입의 이점 중 하나 인 다형성을 활용할 수 없습니다.

프로필 설정이 해당 프로필과 관련된 것 같습니다. 이 경우 다음 중 하나를 수행합니다.

  1. 프로필 생성자 내에서 설정 개체를 인스턴스화

  2. 생성자에서 설정 오브젝트를 전달하고 프로파일에 적용되는 개별 필드를 복사하십시오.

솔직히 Settings 개체를 전달한 다음 Settings 개체 deactivateProfile의 내부 필드를 수정하면 코드 냄새가납니다. 설정 개체는 내부 필드를 수정하는 유일한 개체 여야합니다.


5
The example you give is not dependency injection in the classical sense.-상관 없습니다. OO 사람들은 뇌에 물건을 가지고 있지만 여전히 무언가에
Robert Harvey

1
고전적인 의미에서 대해 이야기 할 때 , @RobertHarvey가 말했듯이 순수하게 OO 용어로 말하면됩니다. 예를 들어 함수형 프로그래밍에서 한 함수를 다른 함수에 삽입 (고차 함수)하는 것은 패러다임의 전형적인 의존성 주입 예제입니다.
David Arno

3
@RobertHarvey : "종속성 주입"으로 너무 많은 자유를 얻고 있다고 생각합니다. 사람들이이 용어를 가장 자주 생각하고 사용하는 장소는 물체를 만들 때 또는 세터 주입의 경우에 바로 "주입"되는 물체의 필드와 관련이 있습니다.
Greg Burghardt

@DavidArno : 그렇습니다. OP는 질문에 객체 지향 PHP 코드 스 니펫이있는 것 같습니다. 따라서 함수 프로그래밍을 다루지 않고 그 점에서만 대답하고있었습니다 .PHP에서는 함수 프로그래밍의 관점에서 동일한 질문을 할 수 있습니다.
Greg Burghardt

7

나는이 파티에 늦게 온다는 것을 알고 있지만 중요한 요점을 놓치고 있다고 생각합니다.

왜 이렇게해야합니까 :

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

해서는 안됩니다. 그러나 의존성 주입이 나쁜 생각이기 때문에 아닙니다. 이것이 잘못하고 있기 때문입니다.

코드를 사용하여이 문제를 살펴 보겠습니다. 우리는 이것을 할 것입니다 :

$profile = new Profile();
$profile->deactivateProfile($setting);

우리가 이것에서 같은 것을 얻을 때 :

$setting->isActive = false; // Deactivate profile

물론 그것은 시간 낭비처럼 보입니다. 당신이 이런 식으로 할 때입니다. 이것은 Dependency Injection을 가장 잘 사용하지 않습니다. 수업을 가장 잘 사용하지는 않습니다.

이제 대신에 우리가 이것을 가지고 있다면 :

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

그리고 이제는 실제로 변화 하고 있다는 사실 을 알 필요없이 application무료로 활성화 및 비활성화 할 profilesetting있습니다. 왜 좋은가요? 설정을 변경해야 할 경우 이 application변화로 인해 벽이 막혔으므로 무언가를 만지는 즉시 모든 것이 깨지는 것을 보지 않고도 안전한 밀폐 공간에서 자유롭게 이동할 수 있습니다.

이것은 행동 원리 와 별개의 구성을 따릅니다 . 여기 DI 패턴은 간단한 것입니다. 필요한 모든 것을 가능한 한 낮은 수준으로 구축하고 함께 연결 한 다음 한 번의 호출로 모든 동작 틱을 시작하십시오.

결과적으로 무엇을 무엇에 연결하고 무엇을 무엇을 말하는지 관리하는 다른 장소를 결정하는 별도의 장소가 생깁니다.

시간이 지남에 따라 유지 관리하고 도움이되지 않는지 확인하십시오.


6

고객이 자동차에 무언가를하기 위해 정비공을 고용 할 때 정비공이 자동차를 처음부터 새로 만들어서 작업 할 것으로 기대하십니까? 아니요, 정비사에게 원하는 자동차제공합니다 .

차고 소유자로서 정비사에게 자동차에 무언가를하도록 지시 할 때 정비사가 자신의 스크루 드라이버 / 렌치 / 자동차 부품을 만들 것으로 기대하십니까? 아니요, 정비사에게 필요한 부품 / 도구를 제공합니다

우리는 왜 이것을 하는가? 생각 해봐 당신은 정비공이되기 위해 누군가를 고용하고 싶은 차고 소유자입니다. 당신은 그것들을 기계공으로 가르치게 될 것입니다 (= 당신은 코드를 작성할 것입니다).

더 쉬워 질 것 :

  • 정비사에게 드라이버를 사용하여 스포일러를 차에 부착하는 방법을 가르쳐주십시오.
  • 정비공에게 차를 만들고, 스포일러를 만들고, 드라이버를 만든 다음, 새로 만든 드라이버를 사용하여 새로 만든 스포일러를 새로 만든 차에 부착하십시오.

정비사가 처음부터 모든 것을 만들지 않으면 큰 이점이 있습니다.

  • 기존 공구 및 부품을 정비사에게 제공하면 교육 (개발)이 크게 단축됩니다.
  • 동일한 기계공이 동일한 작업을 여러 번 수행해야하는 경우, 항상 오래된 기계공을 버리고 새 기계공을 만드는 대신 스크루 드라이버를 재사용 할 수 있습니다.
  • 또한 모든 것을 만드는 법을 배운 기계공은 훨씬 더 전문가가되어야하므로 더 높은 임금을 기대할 것입니다. 여기서 코딩 유추는 책임이 많은 클래스는 엄격하게 정의 된 단일 책임이있는 클래스보다 유지 관리하기가 훨씬 어렵다는 것입니다.
  • 또한 새로운 발명품이 시장에 출시되면 스포일러는 이제 플라스틱 대신 탄소로 만들어집니다. 당신은 당신의 전문 정비사 를 재 훈련 해야합니다 (= 재개발). 그러나 스포일러를 같은 방식으로 부착 할 수있는 한 "간단한"정비공은 재 훈련 할 필요가 없습니다.
  • 자신이 만든 자동차에 의존하지 않는 정비공이 있다는 것은 받는 자동차 를 처리 할 수있는 정비공이 있다는 것을 의미합니다 . 정비공 훈련 당시에는 아직 없었던 자동차를 포함합니다. 그러나 전문가 정비사는 훈련 만들어진 새로운 자동차를 만들 수 없습니다 .

전문 정비사를 고용하고 훈련하면 더 많은 비용을 지불하고 단순한 일이되어야 할 일을 수행하는 데 더 많은 시간이 걸리며 많은 책임 중 하나가 필요할 때마다 영구적으로 재교육을 받아야하는 직원이 생길 것입니다 업데이트 할.

개발 비유는 하드 코딩 된 종속성이있는 클래스를 사용하는 경우 새 버전의 객체 ( Settings귀하의 경우)가 생성 될 때마다 지속적인 재 개발 / 변경이 필요한 클래스를 유지 관리하기가 어려워지고 클래스가 다른 유형의 Settings객체 를 만들 수있는 내부 논리를 개발해야 합니다.

또한 클래스를 소비하는 사람은 클래스 에 전달 하려는 객체 를 단순히 전달할 수있는 것이 아니라 클래스에 올바른 객체 를 작성 하도록 요청 해야합니다. 이는 소비자가 올바른 도구를 만들도록 클래스에 요청하는 방법을 파악하기위한 추가 개발을 의미합니다.SettingsSettings


예, 종속성 반전은 종속성을 하드 코딩하는 대신 쓰기에 약간의 노력이 필요합니다. 예, 더 입력해야하는 것은 성가신 일입니다.

그러나 "변수를 선언하는 데 더 많은 노력이 필요하기 때문에"리터럴 값을 하드 코드하기로 선택하는 것과 같은 주장입니다. 기술적으로는 정확하지만 전문가는 몇 배나 더 많은 단점을 가지고 있습니다 .

응용 프로그램의 첫 번째 버전을 만들 때 종속성 반전의 이점이 발생하지 않습니다. 초기 버전을 변경하거나 확장해야 할 때 종속성 반전의 이점이 발생합니다. 그리고 처음부터 제대로하고 코드를 확장 / 변경할 필요가 없다고 생각하지 마십시오. 사물을 바꿔야합니다.


이것이 전체적으로 성능에 영향을 줍니까?

이것은 응용 프로그램의 런타임 성능에 영향을 미치지 않습니다. 그러나 개발자 의 개발 시간 (따라서 성능)에 큰 영향을줍니다 .


2
" 작은 의견으로, 귀하의 질문은 의존성 주입이 아닌 의존성 반전에 중점을 둡니다. 주입은 반전을 수행하는 한 가지 방법이지만 유일한 방법은 아닙니다. ", 이것은 훌륭한 답변이 될 것입니다. 의존성 반전은 주입 또는 로케이터 / 글로벌을 통해 달성 될 수 있습니다. 질문의 예는 주입과 관련이 있습니다. 따라서 문제 의존성 주입과 의존성 반전에 관한 것입니다.
David Arno

12
나는 모든 차가 약간 애매하고 혼란 스럽다고 생각한다
Ewan

4
@Flater, 문제의 일부는 아무도 의존성 주입, 의존성 반전 및 제어 반전의 차이점에 대해 실제로 동의하지 않는 것 같습니다. 한 가지 확실한 점은, "컨테이너"는 메소드 나 생성자에 의존성을 주입 할 필요가 없다는 것입니다. 순수한 (또는 가난한 사람의) DI는 구체적으로 수동 의존성 주입을 설명합니다. 컨테이너와 관련된 "매직"을 싫어할 때 개인적으로 사용하는 유일한 의존성 주입입니다.
David Arno

1
@Flater이 예제의 문제점은 이런 식으로 코드에서 어떤 문제도 거의 모델링하지 않는다는 것입니다. 따라서 부자연스럽고 강요되며 답변보다 더 많은 질문을 추가합니다. 그것은 증명하는 것보다 오도하고 혼동 할 가능성이 더 높습니다.
jpmc26

2
@ gbjbaanb : 내 대답은 원격으로 다형성을 탐구하지 않습니다. 상속, 인터페이스 구현 또는 유형의 업 / 다운 캐스팅과 유사한 것은 없습니다. 답을 완전히 잘못 읽고 있습니다.
Flater

0

모든 패턴과 마찬가지로 부풀어 오른 디자인을 피하기 위해 "이유"를 묻는 것은 매우 유효합니다.

의존성 주입의 경우 OOP 디자인의 가장 중요한 두 가지 측면을 생각하면 쉽게 알 수 있습니다.

낮은 커플 링

컴퓨터 프로그래밍의 커플 링 :

소프트웨어 엔지니어링에서 커플 링은 소프트웨어 모듈 간의 상호 의존도입니다. 두 루틴 또는 모듈이 얼마나 밀접하게 연결되어 있는지 측정; 모듈 간의 관계의 강점.

낮은 커플 링 을 달성하려고합니다 . 두 가지가 강력하게 결합되면 하나를 변경하면 다른 하나도 변경해야 할 가능성이 높습니다. 하나의 버그 또는 제한은 다른 하나의 버그 / 제한을 유발할 수 있습니다. 등등.

다른 클래스의 객체를 인스턴스화하는 한 클래스는 매우 강한 결합입니다. 하나는 다른 클래스의 존재에 대해 알아야하기 때문입니다. 인스턴스화하는 방법 (생성자가 필요로하는 인수)을 알아야하며 생성자를 호출 할 때 해당 인수를 사용할 수 있어야합니다. 또한 언어에 명시 적 해체 (C ++)가 필요한지 여부에 따라 더 복잡한 문제가 발생할 수 있습니다. 새로운 클래스를 소개한다면 (즉, a NextSettings또는 무엇이든), 원래 클래스로 돌아가서 생성자에 더 많은 호출을 추가해야합니다.

높은 응집력

응집력 :

컴퓨터 프로그래밍에서 응집력은 모듈 내부의 요소가 서로 속하는 정도를 나타냅니다.

이것은 동전의 다른 쪽입니다. 하나의 코드 단위 (하나의 메소드, 하나의 클래스, 하나의 패키지 등)를 살펴보면 가능한 한 적은 책임을 갖도록 해당 단위 내의 모든 코드를 갖기를 원합니다.

이에 대한 기본 예는 MVC 패턴입니다. 도메인 모델을 뷰 (GUI)와이를 서로 연결하는 제어 계층에서 명확하게 분리합니다.

이것은 많은 다른 일을하는 큰 덩어리를 얻는 코드 팽창을 피합니다. 일부를 변경하려면 버그 등을 피하려고 다른 모든 기능도 추적해야합니다. 그리고 나가기가 매우 어려운 구멍에 빠르게 자신을 프로그래밍하십시오.

종속성 주입을 사용하면 DI를 구현하는 모든 클래스 (또는 구성 파일)에 대한 종속성을 작성하거나 추적 할 수 있습니다. 다른 클래스는 정확히 무슨 일이 일어나고 있는지에 대해 신경 쓰지 않을 것입니다-그들은 일반적인 인터페이스로 작업 할 것이고 실제 구현이 무엇인지 전혀 모릅니다. 즉 , 다른 것들에 대해 책임지지 않습니다 .


-1

문제가 발생했을 때 해결하는 데 도움이되는 기술을 사용해야합니다. 의존성 반전과 주입은 다르지 않습니다.

종속성 반전 또는 인젝션은 코드에서 런타임에 호출 할 메소드 구현을 결정할 수있는 기술입니다. 이는 후기 바인딩의 이점을 최대화합니다. 언어가 비 인스턴스 기능의 런타임 대체를 지원하지 않는 경우이 기술이 필요합니다. 예를 들어, Java에는 정적 메소드에 대한 호출을 다른 구현에 대한 호출로 대체하는 메커니즘이 없습니다. 함수 호출을 대체하는 데 필요한 모든 것은 이름을 다른 함수에 바인딩하는 것입니다 (함수를 보유한 변수를 재 지정).

왜 우리는 함수의 구현을 바꾸고 싶습니까? 두 가지 주요 이유가 있습니다.

  • 테스트 목적으로 가짜를 사용하고 싶습니다. 이를 통해 실제로 데이터베이스에 연결하지 않고도 데이터베이스 페치에 의존하는 클래스를 테스트 할 수 있습니다.
  • 여러 구현을 지원해야합니다. 예를 들어 MySQL과 PostgreSQL 데이터베이스를 모두 지원하는 시스템을 설정해야 할 수 있습니다.

제어 컨테이너의 역전을 기록 할 수도 있습니다. 이 의사 코드처럼 보이는 얽힌 거대한 구성 트리를 피하는 데 도움이되는 기술입니다.

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

그것은 당신이 수업을 등록하고 당신을 위해 구성을 할 수 있습니다 :

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

등록 된 클래스가 stateless singleton 일 수있는 것이 가장 간단합니다 .

주의의 말씀

의존성 반전해야합니다 하지 당신의 일 이동 - 로직 디커플링에 대한 답변. 대신 매개 변수화 를 사용할 기회를 찾으십시오 . 다음과 같은 의사 코드 방법을 고려하십시오.

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

이 방법의 일부에 의존성 반전을 사용할 수 있습니다.

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

그러나 우리는 적어도 완전히해서는 안됩니다. 로 상태 저장 클래스를 만들었습니다 Querier. 이제 본질적으로 전역 연결 개체에 대한 참조를 보유합니다. 이것은 프로그램의 전반적인 상태를 이해하는 데 어려움이 있고 다른 클래스가 서로 어떻게 조정되는지와 같은 문제를 만듭니다. 또한 평균화 논리를 테스트하려는 경우 쿼리 자 또는 연결을 가짜로 만들어야합니다. 더 나은 접근 방법은 매개 변수화 를 늘리는 것입니다 .

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

또한 연결은 전체적으로 작업을 담당하고이 출력으로 수행 할 작업을 알고있는 더 높은 수준에서 관리됩니다.

이제 쿼리와는 독립적으로 평균화 로직을 테스트 할 수 있으며 더 다양한 상황에서 더 많은 로직을 사용할 수 있습니다. 우리는 심지어 객체 MyQuerierAverager객체 가 필요한지 여부에 의문을 가질 수 있으며, 아마도 단위 테스트 StuffDoer를 원하지 않는다면 단위 테스트 StuffDoer가 데이터베이스에 너무 밀접하게 결합되어 있기 때문에 단위 테스트 가 완벽하게 합리적이지 않을 수도 있습니다. 통합 테스트에서이를 다루는 것이 더 합리적 일 수 있습니다. 이 경우, 우리는 좋은 결정이 될 수 fetchAboveMinaverageData정적 메소드로합니다.


2
" 의존성 주입은 거대한 얽힌 건설 트리를 피하는 데 도움이되는 기술입니다 ... ". 이 주장 이후의 첫 번째 예는 순수하거나 가난한 사람의 의존성 주입의 예입니다. 두 번째는 IoC 컨테이너를 사용하여 이러한 종속성의 주입을 "자동화"하는 예입니다. 둘 다 작동중인 의존성 주입의 예입니다.
David Arno

@DavidArno 그래, 맞아. 용어를 조정했습니다.
jpmc26

구현을 변경하거나 구현이 다를 수 있다고 가정 할 때 최소한 코드를 디자인해야하는 세 번째 주된 이유가 있습니다. 개발자가 느슨하게 커플 링을하도록 장려하고 새로운 구현을 언젠가 구현할 경우 변경하기 어려운 코드 작성을 피하도록 장려합니다. 미래. 또한 일부 프로젝트에서는 우선 순위가 아닐 수 있지만 (예 : 초기 릴리스 후에 애플리케이션을 다시 방문하지 않을 것이라는 것을 알고 있음) 다른 프로젝트에서는 (예 : 회사 비즈니스 모델이 애플리케이션의 확장 된 지원 / 확장을 제공하려는 경우) ).
Flater

@Flater Dependency 주입은 여전히 ​​강력한 결합을 초래합니다. 논리를 특정 인터페이스에 연결하고 해당 인터페이스가 수행하는 작업에 대해 알고있는 코드가 필요합니다. DB에서 가져온 결과를 변환하는 코드를 고려하십시오. DI를 사용하여 분리하더라도 코드는 여전히 DB 반입이 발생하고이를 호출해야한다는 것을 알아야합니다. 더 나은 해결책은 변환 코드 가 페치가 발생하고 있음을 모르는 경우 입니다. 가져 오기를 수행 할 수있는 유일한 방법 은 가져 오기 도구를 가져 오는 대신 가져 오기 결과 를 호출자가 전달하는 것입니다.
jpmc26

1
@ jpmc26 그러나 (댓글) 예에서 데이터베이스는 시작하기 위해 종속성이 될 필요조차 없었습니다. 당연히 종속성을 피하는 것이 느슨한 결합에 더 좋지만 DI는 필요한 종속성을 구현하는 데 중점을 둡니다.
Flater
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.