依赖注入中接口的C#命名空间

时间:2016-02-28 22:01:33

标签: c# dependency-injection interface namespaces dependencies

我想在C#中使用Dependency Injection模式,我想让命名空间中的逻辑尽可能分开。

问题

消耗类的interface应该在哪个命名空间中?

问题的动机

首先让我们做一些"正常"案件。书籍案例将作为第二部分解释的基础。然后,"现实生活"这个问题就出现了。

书籍案例

让我们假设编码器是Alice,并且她使用Alice作为供应商在名称空间中作为顶级名称,以避免与其他编码器发生冲突。在这个例子中,我们假设世界上没有其他的Alices。

假设她创建了3个名称空间:

  • Alice.Invaders - 一款应通过商店提供应用内购买的游戏。
  • Alice.Shop - 多场比赛的可重复使用的商店。
  • Alice.Injector - 可重复使用的服务管理器。

我们假设Shop项目有一个名为interface的{​​{1}},它提供了IShopService方法。

让我们假设Show()有某种控制器,在某些用户操作中想要打开商店。

我们假设服务(如Invaders)是Shop的控制器通过ServiceManager获得的。

Alice.Injector

Invaders本身是一个没有依赖关系的独立项目,所以它不使用"使用"关键字:

Alice.Injector

Alice.Shop

namespace Alice.Injector { public interface IService { // All the services shall implement this interface. // This is necessary as C# is heavily typed and the // Get() method below must return a known type. } public class ServiceManager { static public IService Get( string serviceName ) { IService result; // Do the needed stuff here to get the service. // Alice implements this getter configurable in a text-file // so if she wants to test the invaders with a mock-shop // that does not do real-purchases but fake-ones // she can swap the injected services without changing the // consumer's code. result = DoTheNeededStuff(); return result; } } } 也是一个独立的项目(除了消耗服务的情况)不知道注射器的存在。只是一家商店而且就是这样。

由于Alice认为Bob可能会在某天做出更好的商店,所以在将Alice.Shop分离到接口Shop然后执行之后,她会准备她的类进行依赖注入这篇文章:https://msdn.microsoft.com/library/hh323705%28v=vs.100%29.aspx

为实现这一目标,Alice将使该商店成为与IShop兼容的服务类型,因此Alice决定将Alice.ServiceManager重命名为IShop,它将是一种IShopService

IService

Alice.Invaders

最后,爱丽丝为游戏编码。 using Alice.Injector namespace Alice.Shop { public interface IShopService : IService { public void Show(); } public class Shop : IShopService { public void Show() { // Here Alice puts all the code to open the shop up. } } } 的代码通过Alice.Invaders获得Shop(采用服务的形式),因此它都是干净的代码。

ServiceManager

到目前为止,这一切都很顺利。

现实案例

所以现在......鲍勃(爱丽丝的一位好朋友,众所周知,好奇他们今天没有谈论发送入门信息),这家超级好的商店是不是比那个更好的商店呢?爱丽丝做了。鲍勃从零开始做他的店铺。

所以Bob实现了与爱丽丝的注入器兼容的商店(因为Bob也使用using Alice.Injector using Alice.Shop namespace Alice.Invaders { public class DefaultController { void OnShopClick() { IShopService shop = ServiceManager.Get( "Shop" ) as IShopService; shop.Show(); } } } 在他的项目中注入其他东西)。

Alice.Injector

所以......这里是奇怪的情况!!

  • using Alice.Injector namespace Bob.Shop { public interface IShopService : IService { public void Show(); } public class Shop : IShopService { public void Show() { // Here Bob does a brand new shop from scratch. } } } 界面的命名空间 - 如果Bob按照上面显示的方式在Bob.Shop命名空间内进行购物,那么Alice 必须编辑她用于引用Bob.Shop以获取Bob.Shop接口的代码(丑陋的她必须更改代码中的依赖项,因为它应该使用依赖注入器来消除更改代码中的依赖项)。 / LI>
  • 没有接口的命名空间 - 如果Alice和Bob都在全局命名空间中设置了IShopService,那也很难看,因为有很多事情可能会发生冲突。
  • IShopService界面的命名空间 - 如果Bob使用常识并说"我想要做的是创建商店兼容与Alice的一个,那么我应该实现HER接口"所以鲍勃的代码很可能就像这样:

Bob使用Alice.Shop向后命名空间兼容性的代码:

Alice.Shop

在这种情况下,似乎一切都已到位:

  • Bob可以创建实现namespace Bob.Shop { public class Shop : Alice.Shop.IShopService { public void Show() { // Here Bob does a brand new shop from scratch, // which borrows Alice's interface. } } }
  • Bob.Shop.Shop
  • Alice不需要更改一行代码。
  • Alice.Shop.IShopService在提供Alice.Injector.ServiceManager时能够提供另一个IService

问题

这里依然存在依赖关系:

Bob.Shop.Shop正在将Alice.Invaders投射到Alice.Injector.IService,以便能够调用Alice.Shop.IShopService方法。如果你不进行演员表演,你就不能向商店展示"。

所以最后,你是"取决于"在那个演员表上因此而且#34;某人"需要为您提供界面定义。

如果原来的商店不是由爱丽丝写的,而是由查理写的,那将是"丑陋的"仍需下载并保留Show()项目的副本才能使用Charlie.Shop

因此...

问题

1)Bob.Shop居住的正确名称空间是什么?

2)"替换"项目提供自己的界面或借用原来的界面?

3)原来的店铺应该分成两个项目吗? (比如示例IShopInterfaceAlice.Shop所以Alice.ShopImplementation是veeery slim并且只包含接口?也许Alice.ShopAlice.Shop作为嵌套命名空间,但仍然是两个sepated代码库,以便您可以下载ans Alice.Shop.Implementation而无需下载Alice.Shop

4)这是否像Bob在其项目中包含Alice.Shop.Implementation文件的副本一样简单,因此不需要依赖项?非常难看 - 如果他这样做并且我们希望拥有2家商店并将用户送到一家或其他商店,那就会发生冲突。

感谢。

1 个答案:

答案 0 :(得分:1)

接口,注入器和实现应该在不同的命名空间中。接口应该在Alice.Shop.Interfaces中,并且在该命名空间中不应该有任何实现。您可以更改/隐藏实现,但您应该在依赖注入中坚持使用接口。

  

Alice.Invaders正在将Alice.Injector.IService转换为   Alice.Shop.IShopService以便能够调用Show()方法。   如果你不这样做,你就不能“展示商店”。

您的DefaultController实现不好。如果我想使用它,我不知道我需要哪些服务。它告诉我,我现在不需要任何东西。

你应该使用构造函数注入。

public class DefaultController
 {
   private readonly IShopService _shopService;

   DefaultController(IShopService shopService)
   {
     _shopService=shopService;
   }

   void OnShopClick()
   {
     _shopService.Show();
   }
  }

如果我需要defaultcontroller,我会知道这个实现需要哪些服务。你不需要施放。

修改

让我们说爱丽丝有一家商店。她说我想要一间有5把椅子的阅览室。但她会决定椅子是木头还是皮革(IChairs)。当她开店时,她决定使用木椅(为IChairs注入WoodChairs)。

然后鲍勃从爱丽丝那里买了这家店。他不能改变阅览室(这很难,需要时间,阅览室也很好)。但他想要皮椅,所以他使用皮椅(用于IChairs的Inject LeatherChairs)。

如果他不能或不想改变阅览室,鲍勃应该坚持Alice.Shop.Interfaces

但是,让我们说。我很喜欢爱丽丝阅览室。我想设计一个像她一样的阅览室。但是我想为阅览室制定规则(IMyReadingRoom适配器,你得到的是ReadingRoom类而不是接口,你创建了自己的接口)。

简而言之:您应该始终坚持接口。您可以为第三方库创建自己的界面(Adapter)。这样你可以扩展或隐藏规则而不必坚持使用第三方库(但你应该坚持使用自己的界面)。您应该为第三方库实现编写适配器而不是它们的接口。

如果我们跳过适配器选择,Bob必须使用Alice.Shop.Interfaces进行注入。