문제가 발생했을 때 해결하는 데 도움이되는 기술을 사용해야합니다. 의존성 반전과 주입은 다르지 않습니다.
종속성 반전 또는 인젝션은 코드에서 런타임에 호출 할 메소드 구현을 결정할 수있는 기술입니다. 이는 후기 바인딩의 이점을 최대화합니다. 언어가 비 인스턴스 기능의 런타임 대체를 지원하지 않는 경우이 기술이 필요합니다. 예를 들어, 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);
}
}
또한 연결은 전체적으로 작업을 담당하고이 출력으로 수행 할 작업을 알고있는 더 높은 수준에서 관리됩니다.
이제 쿼리와는 독립적으로 평균화 로직을 테스트 할 수 있으며 더 다양한 상황에서 더 많은 로직을 사용할 수 있습니다. 우리는 심지어 객체 MyQuerier
와 Averager
객체 가 필요한지 여부에 의문을 가질 수 있으며, 아마도 단위 테스트 StuffDoer
를 원하지 않는다면 단위 테스트 StuffDoer
가 데이터베이스에 너무 밀접하게 결합되어 있기 때문에 단위 테스트 가 완벽하게 합리적이지 않을 수도 있습니다. 통합 테스트에서이를 다루는 것이 더 합리적 일 수 있습니다. 이 경우, 우리는 좋은 결정이 될 수 fetchAboveMin
와 averageData
정적 메소드로합니다.