생성자 주입이란 무엇입니까?


47

(서비스 로케이터) 디자인 패턴에 대한 기사를 살펴보면서 생성자 주입 및 종속성 주입이라는 용어를 살펴 보았습니다.

생성자 주입에 대해봤을 때 결과가 명확하지 않아 여기에서 체크인하라는 메시지가 표시되었습니다.

생성자 주입이란 무엇입니까? 이것은 특정 유형의 의존성 주입입니까? 표준적인 예가 큰 도움이 될 것입니다!

편집하다

일주일이 지난 후에이 질문을 다시 보았을 때, 나는 어떻게 잃어 버렸는지 알 수 있습니다 ... 다른 누군가가 튀어 나오는 경우를 대비하여, 나는 약간의 학습을 통해 질문 본문을 업데이트 할 것입니다. 의견 / 수정은 언제든지 주시기 바랍니다. 생성자 주입과 속성 주입은 두 가지 유형의 종속성 주입입니다.


5
구글에서 첫 번째 명중은 명확하게 설명 ... misko.hevery.com/2009/02/19/…
user281377 10

답변:


95

나는 전문가는 아니지만 도와 줄 수 있다고 생각합니다. 네, 특정 유형의 의존성 주입입니다.

면책 조항 :이 대부분 은 Ninject Wiki 에서 "도난당했습니다"

간단한 예를 통해 의존성 주입 개념을 살펴 보자. 고귀한 전사들이 큰 영광을 위해 싸우는 다음 블록 버스터 게임을 작성한다고 가정 해 봅시다. 먼저 전사를 무장시키기에 적합한 무기가 필요합니다.

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

그런 다음 전사를 나타내는 클래스를 만들어 봅시다. 적을 공격하려면 전사는 Attack () 메소드가 필요합니다. 이 메소드가 호출되면 Sword를 사용하여 적을 공격해야합니다.

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

이제 사무라이를 만들고 전투를 할 수 있습니다!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

당신이 상상할 수 있듯이 이것은 Chopped the evildoers 를 콘솔 에 반 으로 깨끗하게 인쇄 합니다. 이것은 잘 작동하지만 사무라이를 다른 무기로 무장시키고 싶다면 어떨까요? 검은 사무라이 클래스의 생성자 내부에서 생성되므로이 변경을 수행하려면 클래스 구현을 수정해야합니다.

클래스가 구체적인 종속성에 의존 할 때 클래스는 해당 클래스에 밀접하게 연결 되어 있다고합니다 . 이 예제에서 사무라이 클래스는 소드 클래스와 밀접하게 연결되어 있습니다. 클래스가 밀접하게 결합되면 구현을 변경하지 않고 교환 할 수 없습니다. 클래스를 단단히 결합하지 않기 위해 인터페이스를 사용하여 간접적 인 수준을 제공 할 수 있습니다. 게임에서 무기를 나타내는 인터페이스를 만들어 봅시다.

interface IWeapon
{
    void Hit(string target);
}

그런 다음 Sword 클래스는 다음 인터페이스를 구현할 수 있습니다.

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

사무라이 수업을 변경할 수 있습니다.

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

이제 사무라이는 다른 무기로 무장 할 수 있습니다. 하지만 기다려! 이 검은 여전히 ​​사무라이의 생성자 안에 생성됩니다. 전사에게 다른 무기를 제공하기 위해 사무라이의 구현을 변경해야하므로 사무라이는 여전히 검과 밀접하게 연결되어 있습니다.

다행히도 쉬운 해결책이 있습니다. 사무라이 생성자 내에서 소드를 생성하는 대신 생성자의 매개 변수로 노출 할 수 있습니다. 생성자 주입이라고도합니다.

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

조르지오가 지적했듯이 재산 주입도 있습니다. 그것은 다음과 같습니다.

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

도움이 되었기를 바랍니다.


8
이것을 좋아했습니다! :) 그것은 실제로 이야기처럼 읽습니다! 그냥 궁금해서 동일한 사무라이를 여러 개의 무기로 (무기 적으로) 무장시키고 싶다면 속성 주입이 가장 좋은 방법입니까?
TheSilverBullet

네, 그렇게 할 수 있습니다. 실제로, IWeapon을 Attack 메소드의 매개 변수로 전달하는 것이 더 좋습니다. "공공 무효 공격 (Iweapon with, string target)"과 같습니다. 중복 코드를 피하기 위해 기존 메소드 "public void Attack (string target)"을 그대로 유지하고 "this.Attack (this.weapon, target)"이라고합니다.
루이즈 안젤로

그리고 팩토리와 결합하면 CreateSwordSamurai ()와 같은 메소드를 얻을 수 있습니다! 좋은 예입니다.
Geerten

2
좋은 예입니다!
Serge van den Oever

@ 루이스 안젤로. 죄송합니다, 귀하의 의견을 이해하지 못했습니다. "실제로 IWeapon을 Attack 메서드에 대한 매개 변수로 전달하는 것이 좋습니다." 사무라이 클래스에서 Attack 메서드를 오버로드한다는 의미입니까?
Pap

3

나는 이것을 매우 기초가되도록 최대한 노력할 것이다. 이렇게하면 개념을 기반으로 복잡한 솔루션이나 아이디어를 만들 수 있습니다.

이제 Jubilee라는 교회가 있는데 전 세계에 지부가 있습니다. 우리의 목표는 단순히 각 지점에서 아이템을 얻는 것입니다. DI 솔루션은 다음과 같습니다.

1) IJubilee 인터페이스를 만듭니다.

public interface IJubilee
{
    string GetItem(string userInput);
}

2) IJubilee 인터페이스를 생성자로 사용하고 항목을 반환하는 JubileeDI 클래스를 만듭니다.

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3) 이제 Jubilee의 3 개의 브 래치 즉 JubileeGT, JubileeHOG, JubileeCOV를 작성하십시오. 이는 모두 IJubilee 인터페이스를 상속해야합니다. 그것의 재미를 위해 그들 중 하나를 가상으로 메소드를 구현하게하십시오 :

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4) 그렇습니다! 이제 DI가 실제로 작동하는 것을 보자.

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

컨테이너 클래스의 각 인스턴스에 대해 필요한 클래스의 새 인스턴스를 전달하여 느슨한 연결을 가능하게합니다.

이것이 간단히 개념을 설명하기를 바랍니다.


2

A각 클래스 마다 다른 클래스의 인스턴스가 필요한 클래스가 있다고 가정합니다 B.

class A
{
   B b;
}

의존성 주입은 참조를 직접 관리하는 클래스와 달리 B인스턴스를 관리하는 객체에 의해 참조 가 설정 됨을 의미합니다 .AAB

생성자 주입은에 대한 참조가 B생성자의 매개 변수로 전달되고 생성자에서 A설정 됨을 의미합니다 .

class A
{
    B b;

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

대안은 setter 메소드 (또는 특성)를 사용하여 참조를로 설정하는 것 B입니다. 이 경우, 예를 관리하는 오브젝트 aA필수는 제 생성자를 호출 A나중에 멤버 변수를 설정하는 설정 메소드 호출 A.b전에 a사용된다. 후자의 주입 방법은 객체가 서로에 대한 순환 참조를 포함 할 때 필요합니다. 예를 들어 인스턴스 bB설정된 인스턴스 aA에 대한 역 참조를 포함 하는 경우 a.

** 편집하다**

의견을 해결하기위한 자세한 내용은 다음과 같습니다.

1. 클래스 A는 B 인스턴스를 관리합니다

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. B 인스턴스는 생성자에서 설정됩니다

class A
{
    B b;

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

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. B 인스턴스는 setter 메소드를 사용하여 설정됩니다.

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}

... 클래스 A가 B에 대한 참조를 직접 관리하는 것과 반대로 . 이것은 무엇을 의미 하는가? 코드에서 어떻게 하시겠습니까? (나의 무지를 용서.)
TheSilverBullet

1
사무라이의 예가 훨씬 좋습니다. 변수에 대한 문자 사용을 중지합시다 ...
stuartdotnet

@stuartdotnet : 사무라이 예제가 더 나쁘다는 것에 대해 논쟁하지 않지만 왜 한 글자 변수 사용을 중단합니까? 변수에 특별한 의미는 없지만 자리 표시자인 경우 단일 문자 변수는 훨씬 간결하고 요점에 가깝습니다. 더 긴 이름을 사용 itemA하거나 objectA이름을 더 길게 만드는 것이 항상 더 좋은 것은 아닙니다.
조르지오

1
그럼 당신은 내 지점을 놓친 - 비 Descript, 이하의 이름을 사용하여 읽고 따라 이해하기 어려운, 그리고 점을 설명 할 수있는 좋은 방법이 아니다
stuartdotnet을

@stuartdotnet : 나는 당신의 요점을 놓쳤다 고 생각하지 않습니다 : 당신은 설명적인 이름이 항상 더 좋다고 주장하고 코드를 항상 더 읽기 쉽고 스스로 설명합니다.
조르지오
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.