DI(종속성 주입)는 이해하기 쉬운 개념이 아니며 새 애플리케이션이나 기존 애플리케이션에 적용하는 것은 더욱 혼란스럽습니다. Jess Smith는 C# 및 Java 프로그래밍 언어에서 주입 컨테이너 없이 종속성 주입을 수행하는 방법을 보여줍니다. 이 기사에서는 .NET 및 Java 애플리케이션에서 DI(종속성 주입)를 구현하는 방법을 보여 드리겠습니다. 종속성 주입의 개념은 2000년에 Robert Martin이 "디자인 원리 및 패턴"(이후 약어
SOLID 로 알려짐 )이라는 기사를 작성하면서 처음으로 개발자의 관심을 끌었습니다. SOLID의 D는 DOI(Dependency of Inversion)를 나타내며 나중에 종속성 주입으로 알려졌습니다. 독창적이고 가장 일반적인 정의: 종속성 반전은 기본 클래스가 종속성을 관리하는 방식을 반전한 것입니다.
Copy
Martin의 원본 기사에서는 하위 수준 클래스에 대한 클래스의 종속성을 설명하기 위해 다음 코드를 사용했습니다
WritePrinter
.
void Copy()
{
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}
첫 번째 명백한 문제는 매개변수 목록이나 메소드 유형을 변경하는 경우
WritePrinter
해당 메소드에 대한 종속성이 있는 곳마다 업데이트를 구현해야 한다는 것입니다. 이 프로세스는 유지 관리 비용을 증가시키고 새로운 오류의 잠재적인 원인이 됩니다.
또 다른 문제: Copy 클래스는 더 이상 잠재적인 재사용 후보가 아닙니다. 예를 들어, 키보드에서 입력한 문자를 프린터가 아닌 파일로 출력해야 한다면 어떻게 될까요? 이렇게 하려면 클래스를
Copy
다음과 같이 수정하면 됩니다(C++ 언어 구문).
void Copy(outputDevice dev)
{
int c;
while ((c = ReadKeyboard()) != EOF)
if (dev == printer)
WritePrinter(c);
else
WriteDisk(c);
}
새로운 종속성의 도입에도 불구하고
WriteDisk
상황은 개선되지 않았습니다(오히려 악화되었습니다). "소프트웨어 엔터티, 즉 클래스, 모듈, 함수 등은 확장을 위해 개방되어야 하지만 확장을 위해 폐쇄되어야 합니다." 가감." Martin은 이러한 새로운 조건부 if/else 문이 코드의 안정성과 유연성을 감소시킨다고 설명합니다. 해결책은 쓰기 및 읽기 메서드가
Copy
. 종속성을 "푸시"하는 대신 생성자를 통해 전달됩니다. 수정된 코드는 다음과 같습니다.
class Reader
{
public:
virtual int Read() = 0;
};
class Writer
{
public:
virtual void Write(char) = 0;
};
void Copy(Reader& r, Writer& w)
{
int c;
while((c=r.Read()) != EOF)
w.Write(c);
}
이제 클래스 메소드 및 의 다양한 구현을 통해 클래스를
Copy
쉽게 재사용할 수 있습니다 . 클래스에는 유형 및 의 내부 구조에 대한 정보가 없으므로 다른 구현에서 재사용이 가능합니다. 그러나 이 모든 것이 당신에게 일종의 멍청한 짓처럼 보인다면 아마도 Java와 C#으로 작성된 다음 예제가 상황을 명확하게 해줄 것입니다.
Reader
Writer
Copy
Reader
Writer
Java 및 C#의 예
DI
종속성 컨테이너 없이 종속성 주입이 얼마나 쉬운지 설명하기 위해 몇 단계만으로 사용자 정의할 수 있는 간단한 예제부터 시작해 보겠습니다 .
HtmlUserPresentation
메서드가 호출될 때 HTML 사용자 인터페이스를 생성하는 클래스가 있다고 가정해 보겠습니다 . 간단한 예는 다음과 같습니다.
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
이 클래스 코드를 사용하는 모든 프로젝트는 클래스에 대한 종속성을 가지며
HtmlUserPresentation
위에서 설명한 유용성 및 유지 관리 문제가 발생합니다. 개선 사항은 즉시 제안됩니다. 현재 클래스에서 사용할 수 있는 모든 메서드의 시그니처로 인터페이스를 생성하는 것입니다
HtmlUserPresentation
. 다음은 이 인터페이스의 예입니다.
public interface IHtmlUserPresentation {
String createTable(ArrayList rowVals, String caption);
String createTableRow(String tableCol);
}
HtmlUserPresentation
인터페이스를 생성한 후 이를 사용하도록 클래스를 수정합니다 . type 인스턴스화로 돌아가서
HtmlUserPresentation
이제 기본 유형 대신 인터페이스 유형을 사용할 수 있습니다.
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
인터페이스를 생성하면
IHtmlUserPresentation
. 예를 들어, 이 유형을 테스트하려는 경우 기본 유형을
HtmlUserPresentation
이라는 다른 유형으로 쉽게 바꿀 수 있습니다
HtmlUserPresentationTest
. 지금까지 변경한 사항으로 인해 코드를 더 쉽게 테스트, 유지 관리 및 확장할 수 있지만
HtmlUserPresentation
해당 유형을 사용하는 모든 클래스가 여전히 해당 유형의 존재를 인식하므로 재사용할 수 없습니다.
IHtmlUserPresentation
이러한 직접적인 종속성을 제거하려면 이를 사용할 클래스나 메서드의 생성자(또는 메서드 매개 변수 목록)에 인터페이스 유형을 전달할 수 있습니다 .
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
생성자는
UploadFile
이제 유형의 모든 기능에 액세스할 수
IHtmlUserPresentation
있지만 이 인터페이스를 구현하는 클래스의 내부 구조에 대해서는 아무것도 모릅니다. 이러한 맥락에서 유형 주입은 클래스의 인스턴스가 생성될 때 발생합니다
UploadFile
.
IHtmlUserPresentation
다른 기능이 필요한 다른 클래스나 메서드에 다른 구현을 전달하면 인터페이스 유형을 재사용할 수 있습니다.
자료 통합을 위한 결론 및 권장 사항
종속성 주입에 대해 배웠고, 클래스 중 하나가 대상 유형의 기능에 액세스하기 위해 다른 클래스를 인스턴스화할 때 클래스가 서로 직접적으로 종속된다고 배웠습니다. 두 유형 간의 직접적인 종속성을 분리하려면 인터페이스를 생성해야 합니다. 인터페이스는 필요한 기능의 컨텍스트에 따라 다양한 구현을 포함할 수 있는 기능을 유형에 제공합니다. 인터페이스 유형을 클래스 생성자 또는 메소드에 전달하면 해당 기능이 필요한 클래스/메서드는 인터페이스를 구현하는 유형에 대한 세부 정보를 알 수 없습니다. 이로 인해 인터페이스 유형은 유사하지만 동일하지는 않은 동작을 요구하는 여러 클래스에서 재사용될 수 있습니다.
- 종속성 주입을 실험하려면 하나 이상의 애플리케이션에서 코드를 살펴보고 많이 사용되는 기본 유형을 인터페이스로 변환해 보세요.
- 이 기본 유형을 직접 인스턴스화하는 클래스를 변경하여 이 새로운 인터페이스 유형을 사용하고 이를 사용할 클래스 메소드의 생성자 또는 매개변수 목록을 통해 전달하십시오.
- 이 인터페이스 유형을 테스트하기 위한 테스트 구현을 만듭니다. 코드가 리팩터링되면
DI
구현이 더 쉬워지고 재사용 및 유지 관리 측면에서 애플리케이션이 얼마나 유연해지는지 알게 될 것입니다.
GO TO FULL VERSION