问题
I have a console application where I need to execute a certain feature based on input from the user. If the user puts in "feature 1" -> I execute Feature 1, and so on.
I am trying to write this project as clean and as generic as possible, and I want to use the IoC and SOLID concepts, and i am kinda stuck.
What I have so far:
public interface IFeature
{
String execFeature();
}
and
interface IFeatureFactory
{
IFeature createFeature(String input);
}
My first thought was just to have a switch case on the concrete Factory class about the input from the user, and create the concrete Feature accordingly, but I bet there is a better way to do it with IoC.
I read about Ninject factory extension, but didn't understand how to use it in my project.
Whatis the best way to do the factory pattern with IoC/Ninject?
回答1:
If your IFeature implementations does not have other dependencies than using your approach is fine and very simple.
For example lets say you have 2 implementations of IFeature - SomeFeature and OtherFeature that both have parametersless constructor.
Your factory implementation as you suggest would be something like that:
public class FeatureFactory: IFeatureFactory
{
IFeature CreateFeature(string input)
{
if(input=="SomeFeature")
{
return new SomeFeature();
}
else
{
return new OtherFeature ();
}
}
}
However when your IFeature implementations have their own dependencies using this approach you lose the point of using Ninject and IoC.
For example lets say That SomeFeature looks something like that:
public class SomeFeature : IFeature
{
private readonly IDependency1 _dependency1;
private readonly IDependency2 _dependency2;
public SomeFeature (IDependency1 dependency1, IDependency2 dependency2)
{
_dependency1=dependency1;
_dependency2=dependency2;
}
string execFeature()
{
//Some code here...
}
}
And OtherFeature is similar...
public class OtherFeature: IFeature
{
private readonly IDependency1 _dependency1;
private readonly IDependency2 _dependency2;
public OtherFeature(IDependency1 dependency1, IDependency2 dependency2)
{
_dependency1=dependency1;
_dependency2=dependency2;
}
string execFeature()
{
//Some code here...
}
}
Now your factory would become something like that:
public class FeatureFactory: IFeatureFactory
{
IFeature CreateFeature(string input)
{
if(input=="SomeFeature")
{
return new SomeFeature(new Dependency1Implementation(), new Dependency2Implementation());
}
else
{
return new OtherFeature(new Dependency1Implementation(), new Dependency2Implementation());
}
}
}
This is the place when you can use the power of the ninject.extensions.factory
by using the container to solve this dependencies for you.(This dependencies can have their own dependencies and it can get messy very quickly).
As other mentioned you can bind every IFeature implementation using named binding.
Bind<IFeature>().To<SomeFeature>().Named("SomeFeature");
Bind<IFeature>().To<OtherFeature>().Named("OtherFeature");
Of Course you should bind other dependencies as well
Bind<IDependency1>().To<Dependency1Implementation>();
Bind<IDependency2>().To<Dependency2Implementation>();
And then bind the IFeatureFactory to Factory using the factory extension.
Bind<IFeatureFactory>().ToFactory();
What you have to do is create factory method for each of your IFeature implementations in IFeatureFactory and call it Get... according to the Feature named binding.
public interface IFeatureFactory
{
IFeature GetSomeFeature();
IFeature GetOtherFeature();
}
Now ninject will implement(!) this class for you and know which implementation to choose for each method.(There is no need for service locator....)
You can use switch statement over the input in your client to choose which factory method to call or you can wrap it in some provider class that will have the switch statement in it, in both cases you will not have to do the 'new' for IFeature implementations yourself.
Of Course you can pass parameters to the implementations constructors by the factory methods if you need to and other more complex things.
I suggest you to read this for further information.
Edit
I would like to emphasis you don't have to write factory method for each implementation, you can use the same method for all (It is possible but more complex).
To do it you will need to create custom instance provider to detect which implementation to instantiate (according to the factory parameters for example), more about this in the link above and here.
回答2:
You can use Named Bindings
. Example code:
Bind<IFeature>().To<Feature1>().Named("Feature1");
Bind<IFeature>().To<Feature2>().Named("Feature2");
For more info
Edit
If you don't like Service locator pattern
, above approach is not good for your case because you have to use IKernel
to resolve IFeature
.
回答3:
One of the main idea of IoC that you should not have dependencies between components of your solution. So it's good approach to use only interfaces and don't create new instances of your classes with keyword "new". Your issue can't be solved in a simple and elegant way because you can inject only interface which all you features implement.
So you have some features and them implementations:
internal interface IFeature
{
}
internal interface IFeature1 : IFeature
{
}
internal interface IFeature2 : IFeature
{
}
And a factory:
internal interface IFeatureFactory
{
IFeature GetInstance(string featureName);
}
internal class FeatureFactory : IFeatureFactory
{
private readonly ITypeFactory<IFeature1> feature1;
private readonly ITypeFactory<IFeature1> feature2;
private readonly Dictionary<string, ITypeFactory<IFeature>> featuresContainer;
public FeatureFactory(ITypeFactory<IFeature1> feature1, ITypeFactory<IFeature1> feature2)
{
this.feature1 = feature1;
this.feature2 = feature2;
featuresContainer = new Dictionary<string, ITypeFactory<IFeature>>
{
{"Feature1", feature1},
{"Feature2", feature1}
};
}
public IFeature GetInstance(string featureName)
{
if (!featuresContainer.ContainsKey(featureName))
throw new Exception(string.Format("Can't create feature {0}", featureName));
return featuresContainer[featureName].Create();
}
}
You can inject all this stuff in this way:
Bind<IFeatureFactory>().To<FeatureFactory>().InSingletonScope();
Bind<IFeature1>().To<Feature1>();
Bind<IFeature2>().To<Feature2>();
Bind<ITypeFactory<IFeature1>>().ToFactory();
Bind<ITypeFactory<IFeature2>>().ToFactory();
The main idea is that you have only one instance of feature factory for application and you store injected factories of your features. So when you access to IFeatureFactory first time Ninject will create a singleton instance of it. But your features instances will create only when you'll call GetInstance() method.
To make this code work you should add new interface:
public interface ITypeFactory<out T>
{
T Create();
}
And install NuGet package: https://www.nuget.org/packages/Ninject.Extensions.Factory/
回答4:
I have to apply the following methods for Ninject, IoC and factory pattern.
Step 1: Added binding to IOC container
Bind<IFeature>().To<SomeFeature>().Named(nameof(SomeFeature));
Bind<IFeature>().To<SomeFeature>().Named(nameof(SomeFeature));
Step 2: Create an extension method to resolve your dependency
public class Resolver
{
[Inject]
public IKernal kernal { get; set; }
public T Resolve<T>(string type)
{
return kernal.TryGet<T>(type);
}
}
Step 3 Create an instance of your class
IFeature feature = null;
switch (typeOfFeature)
{
case Feature.SomeFeature:
feature = Resolver.TryResolve<IFeature>(nameof(SomeFeature));
break;
case Feature.OtherFeature:
feature = Resolver.TryResolve<IFeature>(nameof(OtherFeature));
break;
default:
throw new Exception("Type not supported");
}
return feature;
来源:https://stackoverflow.com/questions/36823760/c-sharp-ninject-ioc-and-factory-pattern