问题
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 theBob.Shop
namespace the way displayed above, then Alice must edit her code to referenceBob.Shop
to get theIShopService
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 theAlice.Shop.IShopService
- Alice does not need to change a single line of code.
- The
Alice.Injector.ServiceManager
is able to provide anotherIService
when serving theBob.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