나에게 처음 시작할 때 코드를 더 쉽고 빠르게 작성하기 위해 그것들을 보지 않을 때만 이것에 대한 요점이 분명 해졌다. 이것은 그들의 목적이 아니다. 그들은 여러 가지 용도로 사용됩니다.
(이것의 사용을 시각화하는 것은 쉽지 않기 때문에 이것은 피자 비유를 잃을 것입니다)
화면에서 간단한 게임을 만들고 있다고 말하면 상호 작용하는 생물이 있습니다.
A : 프론트 엔드와 백엔드 구현 사이에 느슨한 결합을 도입하여 향후 코드를보다 쉽게 유지 관리 할 수 있습니다.
트롤 만있을 것이므로 이것을 시작할 수 있습니다.
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
프런트 엔드 :
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
2 주가 지나면 마케팅 부서에서 Orcs가 필요하다고 판단합니다. 그들은 트위터에서 그들에 대해 읽을 때 다음과 같이해야합니다.
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
프런트 엔드 :
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
그리고 이것이 어떻게 지저분 해지는 지 알 수 있습니다. 여기에서 인터페이스를 사용하여 프론트 엔드를 한 번 작성하고 (중요한 비트는 테스트) 테스트 한 다음 필요에 따라 추가 백엔드 항목을 꽂을 수 있습니다.
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
프런트 엔드는 다음과 같습니다.
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
프론트 엔드는 이제 ICreature 인터페이스에만 관심을 갖습니다. 트롤이나 오크의 내부 구현에 신경 쓰지 않고 ICreature를 구현한다는 사실에만 신경 쓰지 않습니다.
이 관점에서 이것을 볼 때 주목해야 할 중요한 점은 추상 생물체 클래스를 쉽게 사용할 수 있다는 것입니다.이 관점에서 이것은 동일한 효과가 있습니다.
그리고 창조물을 공장으로 추출 할 수 있습니다.
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
그리고 우리의 프론트 엔드는 다음과 같이 될 것입니다.
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
프론트 엔드는 이제 트롤과 오크가 구현 된 라이브러리 (공장이 별도의 라이브러리에있는 경우)에 대한 참조를 가질 필요가 없습니다.
B : 당신은 다른 생물체가 당신의 균질 한 데이터 구조에서 가질 수있는 기능을 가지고 있다고 하자.
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
프런트 엔드는 다음과 같습니다.
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C : 의존성 주입 사용법
프론트 엔드 코드와 백엔드 구현 사이에 매우 느슨한 결합이있을 때 대부분의 의존성 주입 프레임 워크는 작업하기가 더 쉽습니다. 위의 팩토리 예제를보고 팩토리가 인터페이스를 구현하도록하는 경우 :
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
우리의 프론트 엔드는 생성자를 통해 (일반적으로) MVC API 컨트롤러와 같이 이것을 주입 할 수 있습니다.
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
DI 프레임 워크 (예 : Ninject 또는 Autofac)를 사용하면 런타임에 생성자에서 ICreatureFactory가 필요할 때마다 CreatureFactory의 인스턴스가 작성되도록 코드를 설정할 수 있습니다. 이는 코드를 훌륭하고 단순하게 만듭니다.
또한 컨트롤러에 대한 단위 테스트를 작성할 때 조롱 된 ICreatureFactory를 제공 할 수 있습니다 (예를 들어 구체적인 구현에 DB 액세스가 필요하면 단위 테스트를 원하지 않는 경우) 컨트롤러에서 코드를 쉽게 테스트 할 수 있습니다 .
D : 다른 용도가 있습니다. 예를 들어 '레거시'이유로 잘 구성되지 않은 두 개의 프로젝트 A와 B가 있고 A는 B에 대한 참조가 있습니다.
그런 다음 B에서 이미 A에서 메소드를 호출해야하는 기능을 찾으십시오. 순환 참조를 얻을 때 구체적인 구현을 사용하여이를 수행 할 수 없습니다.
A의 클래스가 구현하는 인터페이스를 B로 선언 할 수 있습니다. 구체적인 객체가 A 유형 인 경우에도 B의 메소드에는 문제없이 인터페이스를 구현하는 클래스의 인스턴스가 전달 될 수 있습니다.