对我而言,刚开始时,只有当您停止将它们视为使代码更容易/更快地编写的事情时,这些问题的意义才变得清楚-这不是它们的目的。它们有许多用途:
(这将失去比萨饼的类比,因为很难直观地看到它的用法)
假设您正在屏幕上制作一个简单的游戏,它将有您与之互动的生物。
答:通过在前端和后端实现之间引入松散耦合,它们可以使将来的代码更易于维护。
您可以先写这个,因为只会有巨魔:
// 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);
}
下线两周后,行销决定您也需要兽人,因为他们在Twitter上阅读了有关兽人的信息,因此您必须执行以下操作:
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();
}
现在,前端甚至不必引用实现Troll和Orc的库(前提是工厂位于单独的库中)-不需要任何有关它们的信息。
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(例如,如果具体实现需要数据库访问权限,我们不希望单元测试依赖于此),并且可以轻松地在控制器中测试代码。
D:还有其他用途,例如,您有两个项目A和B,出于“传统”的原因,它们的结构不够完善,A引用了B。
然后,您可以在B中找到需要调用A中已有方法的功能。在获得循环引用时,您无法使用具体的实现来实现它。
您可以在B中声明一个接口,然后由A中的类实现。即使具体对象是A中的类型,也可以向B中的方法传递实现该接口的类的实例,而不会出现问题。