의존성 역전 원리(Dependency Inversion Principle) 관련 용어

의존성 역전 원리(Dependency Inversion Principle)는 모듈간 낮은 결합도(loose coupling)와 테스트 용이성(testability)을 확보하는데 유용한 디자인 방법입니다. 저는 확장 가능하고(scalable) 클라우드 환경에 적응력있는(adaptive) 응용프로그램 디자인을 위해 의존성 역전 원리가 아주 큰 역할을 한다고 생각합니다. 하지만 안타깝게도 의존성 역전 원리와 관련된 기법과 도구를 지칭하는 용어들은 자주 혼동됩니다. 용어가 혼동된다는 것은 그 대상의 의미도 혼동되고 있다는 뜻입니다. 의미를 혼동하면 필요한 곳에 적절히 사용할 수 없겠죠. 의존성 역전 원리에 대한 이해를 돕기 위해 관련된 주요 용어들을 간단히 정리했습니다. 설명 대상 용어에 대해서는 번역된 표현 대신 영어 원문을 그대로 사용했습니다.

Hollywood_Sign_(Zuschnitt)

IoC(Inversion of Control, 제어 역전)

IoC는 전통적인 방식에 반대되는 흐름으로 코드가 진행되는 것을 말하는 일반적인 용어입니다. Dependency Injection과 많이 혼동되는데 다른 개념입니다. 오히려 Dependency Inversion Principle이 IoC의 한 형태입니다.

아주 가끔씩 IoC를 Dependency Injection과 동일한 축소된 의미로의 사용을 허용하는 모습을 볼 수 있는데 저는 이것이 아주 맘에 들지 않습니다. Martin Fowler는 이것과 관련해 이런 말을 했습니다.

“Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.”

라이브러리와 프레임워크의 관계는 IoC를 설명하는 단골 예입니다. 라이브러리를 사용할 때는 내 코드가 라이브러리 코드(외부 코드)를 호출하지만 프레임워크를 사용할 때는 프레임워크(외부 코드)가 내 코드를 호출합니다. 상호작용(interactive) 프로그래밍 모델과 반응형(reactive) 프로그래밍 모델도 하나의 예입니다.

IoC는 헐리웃에서 오디션이 끝난 후 오디션 참가자에게 하는 말때문에 Hollywood Principle로 불리기도 합니다.

Don’t call us, we’ll call you.(합격했는지 귀찮게 자꾸 전화하지마. 합격하면 우리가 너한테 연락할꺼야.)

다음 IoC에 대한 설명을 참고하세요.

별도의 게시물로 IoC에 대해 좀 더 정리했습니다.

Dependency Inversion Principle(DIP, 의존성 역전 원리)

Dependency Inversion Principle은 의존관계를 갖는 모듈 인스턴스의 구성이 추상화에 의존하는 것을 뜻합니다. 말이 조금 어려운데 사전적 정의는 더 어렵습니다.

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

의존 주체 모듈 인스턴스가 의존 대상 모듈 인스턴스를 직접 생성하는 것을 전통적 흐름이라고 하면 이 전통적 흐름에서는 의존 대상 코드의 형식이 의존 주체 코드에 의해 결정되기 때문에 의존 대상 코드의 다형성이 동작하기 어렵습니다. 반면 Dependency Inversion Principle이 사용되면 구체적인 의존 관계가 추상화에 의해 런타임에 결정되기 때문에 다형성을 적극적으로 활용할 수 있으며 모듈의 재사용성이 높아집니다.

Dependency Inversion Principle을 설명하기 위해 문서 편집기(DocumentEditor, 의존 주체)와 문서 저장 기능을 제공하는 인터페이스(ISerializer, 의존 대상) 및 구현체를 정의합니다.

public interface ISerializer
{
    void Serialize(string file, Document document);
}

public class BinarySerializer : ISerializer
{
    public void Serialize(
        string file, Document document)
    {
    }
}

public class JsonSerializer : ISerializer
{
    public void Serialize(
        string file, Document document)
    {
    }
}

public class DocumetEditor
{
    private ISerializer _serializer;

    public Document { get; } = new Document();

    public void Save(string file)
    {
        _serializer.Serialize(file, Document);
    }
}

Dependency Inversion Principle이 사용되지 않은 DocumentEditorISerializer 구현체 인스턴스를 얻는 코드는 다음과 같습니다.

public class DocumentEditor
{
    public DocumentEditor()
    {
        _serializer = new BinarySerializer();
    }
}

DocumentEditorISerializer의 관계는 추상화에 의존하지 않습니다. DocumentEditor는 직접 구체적인 BinarySerializer 클래스 인스턴스를 생성하기 때문에 JsonSerializer와 같은 다른 ISerializer 구현체를 적용할 수 없습니다.

Dependency Injection(의존성 주입)

많은 사람들이 Dependency Injection과 Dependency Inversion Principle을 혼동합니다. Dependency Injection은 Dependency Inversion Principle을 구현하는 기법 중 하나입니다. 의존 주체 모듈의 공용으로(public) 노출된 멤버를 통해 의존 대상 모듈 인스턴스가 제공됩니다. Dependency Injection은 다시 세부적으로 다음과 같이 구분됩니다.

Constructor Injection(생성자 주입)

Dependency Injection 중 가장 많이 사용되는 방법입니다. 의존 주체 모듈의 생성자 매개변수로 의존 대상 모듈 인스턴스가 주입됩니다. 의존 주체 인스턴스가 생성됨과 동시에 의존성이 해결되기 때문에 일단 의존 주체 인스턴스가 생성되면 의존성 해결 여부를 검사할 필요가 없습니다. 정적 언어의 경우 의존성이 해결되지 않으면 의존 주체 인스턴스를 생성할 수 없기 때문에 모듈간 의존 관계가 문법적으로 명확하게 드러나며 의존성이 누락될 위험이 컴파일러에 의해 차단됩니다.

public class DocumentEditor
{
    public DocumentEditor(ISerializer serializer)
    {
        _serializer = serializer;
    }
}

Property Injection(속성 주입)

공용으로 노출된 쓰기 가능한 속성을 통해 의존성을 주입하는 방법입니다. Setter Injection이라고도 부릅니다. 속성이 외부에 노출된 정보를 의미하는지 의존 대상 모듈을 의미하는지 별도의 설명이 없으면 구분하기 어렵습니다. 속성 설정은 의존 주체 인스턴스가 생성된 후에 진행되기 때문에 의존 주체 인스턴스는 필요한 의존성이 해결되지 않은 상태일 수 있습니다.

public class DocumentEditor
{
    public ISerializer Serializer
    {
        set { _serializer = value; }
    }
}

Interface Injection(인터페이스 주입)

솔직히 저는 Interface Injection을 직접 사용한 적은 없습니다. Interface Injection 섹션의 내용은 경험을 토대로 한 것이 아니라 정보를 수집한 수준임을 밝힙니다.

Interface Injection은 주입을 위한 수단(공용 멤버)를 인터페이스를 통해 정의합니다. Property Injection과 유사하지만 인터페이스를 통해 의존성이 노출되기 때문에 의존관계가 명확하며 모듈 사이의 결합도가 추상화를 통해 더 낮아집니다.

public interface ISerializerDependent
{
    ISerializer Serializer { set; }
}

public class DocumentEditor : ISerializerDependent
{
    ISerializer ISerializerDependent.Serializer
    {
        set { _serializer = value; }
    }
}

IoC Container

IoC Container는 Dependency Inversion Principle이 적용된 모듈의 조립을 도와주는 도구입니다. 하지만 Dependency Inversion Principle이 디자인에 사용되었다고 해서 IoC Container가 반드시 필요한 것은 아닙니다. IoC Container를 사용하지 않고 Constructor Injection이 적용된 DocumentEditor 인스턴스를 생성하면 다음과 같습니다.

public class Application
{
    public static void Main()
    {
        ISerializer serializer = new JsonSerializer();
        var documentEditor = new DocumentEditor(serializer);
    }
}

반면 IoC Container를 이용하면 DocumentEditor 클래스의 생성자를 직접 호출하지 않습니다.

public class Application
{
    public static void Main()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<JsonSerializer>().As<ISerializer>();
        builder.RegisterType<DocumentEditor>();
        IContainer container = builder.Build();

        var documentEditor = container.Resolve<DocumentEditor>();
    }
}

보통 IoC Container는 모듈 조립 기능과 더불어 모듈 개체의 수명을 관리하는 기능을 제공합니다. 예를 들어 HTTP 서버의 경우 요청마다 각각 의존 대상 모듈 인스턴스를 만들고 요청에 대한 응답이 완료되면 요청 처리 과정에서 생성된 모든 인스턴스를 삭제(혹은 폐기)할 수 있습니다.

Service Locator

Service Locator는 Dependency Injection과는 다른 Dependency Inversion Principle 적용법입니다. Dependency Injection에서 의존 주체 모듈은 의존성을 외부의 주입에 의해 수동적으로 해결하는 반면 Service Locator를 사용하는 의존 주체 모듈은 의존성이 필요한 시점에 능동적으로 의존성 해결을 Service Locator에 요청합니다.

public class DocumetEditor
{
    public void Save(string file)
    {
        IContainer locator = IocContainer.Current;
        var serializer = locator.Resolve<ISerializer>();
        serializer.Serialize(file, Document);
    }
}

Service Locator는 의존 주체 모듈이 매개변수를 가지지 않는 기본 생성자를 통해 생성되어야 하는 제약조건이 있는 경우나 의존 대상 모듈이 준비되기 전에 의존 주체 모듈 인스턴스가 만들어져야 하는 경우 등에 사용될 수 있습니다.

Dependency Injection과의 차이점은 다음과 같습니다.

  • Service Locator가 적용된 의존 주체 모듈은 공용 API를 통해 외부 모듈 의존관계가 노출되지 않습니다. DocumentEditor의 경우 외부에서는 ISerializer에 의존하고 있음을 알아챌 수 없습니다.
  • 의존 주체 모듈은 IoC Container를 직접 사용하기 때문에 IoC Container의 존재를 알고 있으며 필수적으로 의존합니다.
  • IoC Container가 정적인(static) 코드를 통해 제공되기 때문에 병렬(parallel) 테스팅이 어렵습니다.

의존성 역전 원리(Dependency Inversion Principle) 관련 용어”에 대한 15개의 생각

    1. Gyuwon 글의 글쓴이

      아닙니다. Dependency Injection이 Dependency Inversion 기법이니 그렇게 말해도 틀리지 않지만 제가 말하려는 바는 Dependency Inversion이 맞습니다.

      응답
  1. parkheesung

    설명이 잘 정리되어있음에도 이해하는게 쉽지는 않게 느껴지네요. 그나마 코드로 예를 든 부분은 이해가 빠르게 되지만, 용어 자체가 포괄적인 의미로, 다른 용어에서 재사용되는 느낌입니다.
    헷깔리네요 ㅠㅠ

    응답
    1. Gyuwon 글의 글쓴이

      짧게 정리했을 뿐이니 이 글만 읽어서는 개념을 이해하기 어려운 것이 당연합니다. Dependency Injection 한 주제로 책이 씌여지기도 하는데요. 급할 때 꺼내보는 정도로 활용되는 것이 적절할 것 같습니다.

      응답
  2. jaygoo13

    잘 정리된 글 유익하게 읽었습니다.
    그런데 principal은 ‘수석’, ‘최고’ 라는 의미이고, 원칙이라는 뜻의 principle로 수정하시면 좋을거 같아요 ^^

    응답
  3. wolfkang

    잘 봤습니다. 두 번 봤습니다. 그런데 Depencency Inversion 은 왜 Inversion of Dependency 가 아니고 Dependency Inversion 으로 쓰는 걸까요?

    응답
  4. Thanks

    Nice work Gyuwon. Reading from New Zealand, I found the code examples and explanations interesting. I had not heard of the Dependency Inversion Principle before this and also was not aware of its connection to Dependency Injection.

    Very nice

    응답
  5. 핑백: Ahea Team Study Blog

  6. 권태국

    > Dependency Injection은 Dependency Inversion Principle을 구현하는 기법 중 하나입니다.

    개념적으로 Dependency Inversion Principle 은 Dependency Injection 과는 별개라고 봐야 옳지 않을까 하는 생각이 들어 답글 남겨봅니다~ DI 는 만족하지만 DIP 는 만족하지 않는 경우가 있을 수 있으니까요.

    > Dependence Inversion is about the shape of the object upon which the code depends. How does DIP relate to IoC and DI? Consider what happens if you use DI to inject a low-abstraction dependency? For example, I could use DI to inject a JDBC connection into a Monopoly game so it could use a SQL statement to read the Monopoly board from DB2. While this is an example of DI, it is an example of injecting a (probably) problematic dependency as it exists at an abstraction level significantly lower than the domain of my problem.

    출처 : https://martinfowler.com/articles/dipInTheWild.html

    응답
    1. Gyuwon 글의 글쓴이

      버린 블로그라 댓글이 달려도 반응하지 않는데 이 의견에는 말씀을 드려야 할 것 같네요.

      말씀하신 내용에 동의합니다. DI 통해 DIP를 구현할 수 있다는 의미를 마치 DI가 DIP에 종속된 기법인 것처럼 표현해 버렸네요. 제 실수입니다.

      응답
  7. sungchul Ha

    아주 가끔씩 IoC를 Dependency Injection과 동일한 축소된 의미로의 사용을 허용하는 모습을 볼 수 있는데 저는 이것이 아주 맘에 들지 않습니다. Martin Fowler는 이것과 관련해 이런 말을 했습니다.
    ->
    IoC가 너무 평범한(또는 광범위한) 용어라 사람들이 헷갈려 합니다. 그래서 Dependency Injection으로 이름을 바꿨습니다.

    응답

jaygoo13님에게 덧글 달기 응답 취소