C# namespacing for interfaces in dependency injection

我是研究僧i 提交于 2019-12-13 02:38:02

问题


I want to use the Dependency Injection pattern in C#, and I want to have the logics as separated as possible in namespaces.

Question

In which namespace should the interface of the consumed class be?

Motivation of the question

First let's do some "normal" case. A book-case to be used as the basis for the second part of the explanation. Then, the "real-life" case, which arises the question.

Book case

Let's assume the coder is Alice and that she uses Alice as a top-level name in the namespaces as a vendor, to avoid any conflict with other coders. For this example, we'll assume there are no other Alices in the world.

Let's assume she creates 3 namespaces:

  • Alice.Invaders - a game that shall provide in-app purchases via a shop.
  • Alice.Shop - a reusable shop for several games.
  • Alice.Injector - a reusable service manager.

Let's assume the Shop project has an interface named IShopService which provides a method Show().

Let's assume the Invaders has some kind of controller that at some user-action wants to open the shop.

Let's assume the services, like the Shop, are gotten via a ServiceManager by the controller of the Invaders.

Alice.Injector

The Alice.Injector itself is an independent project that has no dependencies, so it does not use the "using" keyword:

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

The Alice.Shop is also an independent project which (except in the case it consumes services) is not aware of the existance of an injector. Just is a shop and that's it.

As Alice thinks that maybe Bob will make some better shop someday, she prepares her class to be dependency-injected, following the separation of the Shop into an interface IShop and then the implementation, following this article: https://msdn.microsoft.com/library/hh323705%28v=vs.100%29.aspx

To achieve that, Alice will make the shop to be a service kind of compatible with the Alice.ServiceManager, so Alice decides that the IShop will be renamed to IShopService and it will be a kind of 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

Finally, Alice codes the game. The code of the Alice.Invaders gets a Shop (that takes the shape of a service) via the ServiceManager so it is all clean-code.

using Alice.Injector
using Alice.Shop

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

Up to here, this all works nicely.

Real-life case

So now... Bob (a good friend of Alice, as you all know, curious that they don't talk about sending encripted messages today), does a super-nice shop that is even nicer than the one that Alice did. Bob does his shop from scratch.

So Bob implements the shop compatible with Alice's injector (as Bob also uses the Alice.Injector for injecting other stuff in his projects).

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

So... here it is the weird situation!!

  • Bob.Shop Namespace for the interface - If Bob does his shop inside the Bob.Shop namespace the way displayed above, then Alice must edit her code to reference Bob.Shop to get the IShopService interface (ugly she has to change the dependency in the code, as it was supposed to use Dependency Injectors to get rid of changing the dependencies in the code).
  • No namespace for the interface - If both Alice and Bob setup the IShopService in the global namespace, it is ugly too, as there is much things that could conflict there.
  • Alice.Shop Namespace for the interface - If Bob makes use of common sense and says "What I want to do is to create a shop compatible with Alice's one, then I should implement HER interface" so Bob's code most likely will be like this one:

Bob's code using Alice.Shop backwards namespace compatibility:

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

In this case it seems everything is in place:

  • Bob may create the Bob.Shop.Shop that implements the Alice.Shop.IShopService
  • Alice does not need to change a single line of code.
  • The Alice.Injector.ServiceManager is able to provide another IService when serving the Bob.Shop.Shop.

Problem

Still there is a dependency here:

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

So at the end, you are "depending" on that cast and therefore "someone" needs to provide you the interface definition.

If the original shop was not written by Alice, but by Charlie, it would be "ugly" to still have to download and keep a copy of the Charlie.Shop project in order to use Bob.Shop.

So...

Questions

1) What is the correct namespace for IShopInterface to live in?

2) Does the "replacement" project provide it's own interface or borrow the original one?

3) Should maybe the original shop be splitted in TWO projects? (like for example Alice.Shop and Alice.ShopImplementation so Alice.Shop is veeery slim and only contains the interfaces? Maybe Alice.Shop and Alice.Shop.Implementation as a nested namespace, but still two sepated code-bases so you can download ans install Alice.Shop without downloading Alice.Shop.Implementation?

4) Is that as simple as Bob does include a copy of Alice.Shop.IShopInterface file in his project so no dependencies are needed? Very ugly - if he does so and we want to have the 2 shops and send the users to one or other shop, that would conflict.

Thanks.


回答1:


Interfaces, injector and implemantation should be in different namespaces. Interfaces should be in Alice.Shop.Interfaces and there shouldn't be any implemantation in this namespace. You can change/hide implementation, but you should stick with Interfaces in dependency Injection.

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

Your DefaultController implemantation is not good. If I want to use it, I don't know anything about which services I need. It says to me, I don't need anything now.

You should use constructor injection.

public class DefaultController
 {
   private readonly IShopService _shopService;

   DefaultController(IShopService shopService)
   {
     _shopService=shopService;
   }

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

If I need defaultcontroller, I would know which services I need with this implemantation. And you don't need to cast.

Edit:

Let's say Alice has a shop. She says I want a reading room which has 5 chairs. But she will decide chairs are from wood or leather (IChairs). When she openes the shop, she decides to use wood chairs (Inject WoodChairs for IChairs).

Then Bob buys the shop from Alice. He can't change reading room (it's hard it will take time and reading room is just fine). But he wants leather chairs so he uses leather chairs (Inject LeatherChairs for IChairs).

Bob should stick Alice.Shop.Interfaces if he can't or doesn't want to change Reading Room.

But let's say. I like Alice Reading Room much. I want to design a reading room like hers. But I want to make my rules for Reading Room (IMyReadingRoom adapter, you get ReadingRoom class not Interface and you create your own interfaces).

In short: You should stick interfaces always. You can create your own Interface (Adapter) for 3rd party libraries. That's way you can extend or hide the rules without stick to 3rd party library (But you should stick with your own Interface anyway). You should write adapter for 3rd party library implemantation not for their interfaces.

If we skip adapter choice Bob has to use Alice.Shop.Interfaces for injecting.



来源:https://stackoverflow.com/questions/35688838/c-sharp-namespacing-for-interfaces-in-dependency-injection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!