Changing implementation of interface depending on

2019-08-03 09:20发布

问题:

I have an application which uses an interface IBackend for communicating with a backend. In the production environment I wish to use class ProdBackend : IBackend as the implementation of the interface. In the test environment I wish to use TestBackend : IBackend.

The application is packaged into a zip file, which must be independent of whether it is deployed in the prod or test environment.

How can I make the application use a different implementation of IBackend depending on the environment it is deployed in?

Can I do this by simply having to different .dll's installed in the two environments and naming the classes the same?

UPDATE 11:12 - 15/1: The packaged application is not allowed to include the prod implementation, i.e. ProdBackend : IBackend. So the application does not know ProdBackend : IBackend at compile time.

回答1:

Supplying different DLL's with different implementations of the same interfaces would work fine.

An more elegant solution would be to do this but to add a Service Locator that supplies the implementation you need. Then you could configure the Service Locator to return an implementation based on the platform you run on.

There are many service locators out there, but a simple one that runs on all platforms supported by Mono is TinyIOC



回答2:

There are many ways to do this depending on your set up. I can think of these:

  1. Machine Name: If you know the server name (or convention) for the testing server, you can new up the dependecy based on Envirnoment.MachineName.
  2. Plugin: If you use a plugin model (by building an assembly that contains ProdBackend and one the contains TestBackend, you can make the decision when deploying the application. You can do this by having a plugin directory that your IOC container will use to wire up dependencies (or other means).
  3. Configuration: You can use a value in a configuration file, to determine the environment and then use that information to pick between the two implementations. Then, when deploying to the prod environment, you can adjust the configuration file (app.config) accordingly.


回答3:

The entire point of the Test environment is to allow you to be as reasonably sure as possible that when you deploy something to the Production environment it will work properly. If you have one set of code that executes on Test and a different set of code that executes when deployed to Production, how are you accomplishing that goal?

Essentially, the difference between Test and Production from a code perspective should should be configuration only (different db connection, possibly more verbose logging on Test, etc). Otherwise you are, essentially, deploying untested code to Production which is, in my opinion, sowing the seeds for possible disaster.



回答4:

There is a concept: Contextual Binding — that is implemented by almost all dependency injection container. In practice this mean, that you could add the condition to your bindings. Here is example using ninject

Define 'production' environment condition

For example by Environment.MachineName, or any other suitable for you:

private static readonly Func<bool> IsCurrentEnvironmentProduction = 
   () => Environment.MachineName == "Production.Server";

Define bindings for different environments

public static IKernel InitializeKernel() 
{ 
    var kernel = new StandardKernel();

    // binding for production
    kernel
        .Bind<IBackend>()
        .To<ProdBackend>()
        .When(request => IsCurrentEnvironmentProduction());

    // binding for test environment
    kernel
        .Bind<IBackend>()
        .To<TestBackend>()
        .When(request => !IsCurrentEnvironmentProduction());

    return kernel; 
}

Resolve instance depends on environment

DI container will do all for you

var backend = kernel.Get<IBackend>();

Full sample is available here

Remark about Mono

According to Ninject download there is a release for Mono, but you could implement same functionality in other DI containers also