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

C# namespacing for interfaces in dependency injection

我想在 C# 中使用 Dependency Injection 模式,并且我希望在命名空间中尽可能分离逻辑。

问题

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

提问的动机

首先让我们做一些"normal"案例。一个书柜,用作第二部分解释的基础。那么,"real-life"的情况下,问题就来了。

书柜

让我们假设编码员是 Alice,她使用 Alice 作为名称空间中的顶级名称作为供应商,以避免与其他编码员发生任何冲突。对于这个例子,我们假设世界上没有其他爱丽丝。

我们假设她创建了 3 个命名空间:

假设 Shop 项目有一个名为 IShopServiceinterface,它提供了一个方法 Show().

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

让我们假设 Shop 等服务是由 Invaders 的控制器通过 ServiceManager 获得的。

Alice.Injector

Alice.Injector本身是一个独立的项目,没有依赖关系,所以没有使用"using"关键字:

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.Shop

Alice.Shop也是一个独立的项目(除了它使用服务的情况)不知道注入器的存在。只是一家商店而已。

由于 Alice 认为 Bob 可能有一天会开一些更好的店,她准备 class 进行依赖注入,然后将 Shop 分离到接口 IShop然后是实施,遵循这篇文章:https://msdn.microsoft.com/library/hh323705%28v=vs.100%29.aspx

为了实现这一点,Alice 将使商店成为一种与 Alice.ServiceManager 兼容的服务,因此 Alice 决定将 IShop 重命名为 IShopService 并且它将是一种 IService.

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

最后,Alice 编写了游戏代码。 Alice.Invaders 的代码通过 ServiceManager 得到一个 Shop(采用服务的形式),所以它都是干净的代码。

using Alice.Injector
using Alice.Shop

namespace Alice.Invaders
{
    public class DefaultController
    {
        void OnShopClick()
        {
            IShopService shop = ServiceManager.Get( "Shop" ) as IShopService;
            shop.Show();
        }
    }
}

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

真实案例

所以现在... Bob(Alice 的好朋友,众所周知,好奇他们今天不谈论发送加密消息),做了一家超级好的商店,甚至比那个更好爱丽丝做到了。 Bob 从头开始​​经营他的商店。

所以 Bob 实现了与 Alice 的注入器兼容的商店(因为 Bob 也使用 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 的代码使用 Alice.Shop 向后命名空间兼容性:

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.
        }
    }
}

在这种情况下,似乎一切就绪:

问题

这里还是有依赖:

Alice.Invaders 正在将 Alice.Injector.IService 转换为 Alice.Shop.IShopService 以便能够调用 Show() 方法。如果你不做那个演员,你不能 "show the shop".

所以最后,您是 "depending" 演员,因此 "someone" 需要为您提供接口定义。

如果原来的商店不是爱丽丝写的,而是查理写的,那么"ugly"仍然需要下载并保留Charlie.Shop项目的副本才能使用Bob.Shop.

所以...

问题

1) IShopInterface 的正确命名空间是什么?

2) "replacement" 项目是提供自己的接口还是借用原来的接口?

3) 是否应该将原来的商店分成两个项目? (比如 Alice.ShopAlice.ShopImplementation 所以 Alice.Shop 很苗条,只包含接口?也许 Alice.ShopAlice.Shop.Implementation 作为嵌套的命名空间,但仍然有两个分离的代码库,因此您可以下载 ans install Alice.Shop 而无需下载 Alice.Shop.Implementation?

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

谢谢。

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

Alice.Invaders is casting the Alice.Injector.IService to an Alice.Shop.IShopService in order to be able to call the Show() method. If you don't do that cast, you can't "show the shop".

您的 DefaultController 实现不好。如果我想使用它,我对我需要哪些服务一无所知。它对我说,我现在不需要任何东西。

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

public class DefaultController
 {
   private readonly IShopService _shopService;

   DefaultController(IShopService shopService)
   {
     _shopService=shopService;
   }

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

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

编辑:

假设爱丽丝有一家商店。她说我想要一间有 5 把椅子的阅览室。但她会决定椅子是用木头还是皮革制成的 (IChairs)。当她开店时,她决定使用木椅(Inject WoodChairs for IChairs)。

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

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

但是说吧。我很喜欢爱丽丝阅览室。我想设计一个像她一样的阅览室。但我想为阅览室制定规则(IMyReadingRoom 适配器,你得到的是 ReadingRoom class 而不是界面,你可以创建自己的界面)。

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

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