对我来说,刚开始的时候,只有当你不再把它们看作是让你的代码更容易/更快编写的东西时,它们的意义才变得清晰——这不是它们的目的。它们有很多用途:
(这里就没有披萨的比喻了,因为这个比喻的用法不太容易想象)
假设你正在屏幕上制作一款简单的游戏,游戏中会有与你互动的生物。
答:通过在前端和后端实现之间引入松散耦合,它们可以使您的代码在将来更容易维护。
你可以这样写,因为这里只会有喷子:
// 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 = _factory.GetCreature(creatureType);
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中已经存在的方法的功能。由于您获得的是循环引用,因此不能使用具体实现来实现。
你可以在B中声明一个接口,然后由A中的类实现。你在B中的方法可以被传递一个实现接口的类的实例,即使具体对象是a中的类型。