I want to control the creation of a bunch of classes that all share a common interface and all need a bit of logic in the construction. Also, I don't want any other code than the class factory to be able to create objects from these classes.
My main stumbling blocks are:
(1) for the generic method to be able to create instances of the classes I need the new() constraint which means I must have a public constructor on the classes which means they can be created publicly.
(2) An alternative would be for the classes themselves to have a static method which returns an instance of the class. But I can't call that from my generic class because I need to be dealing in terms of interfaces/types and you can't have statics via interfaces.
Here's the kind of thing I've currently got, but it's using the new() constraint which is allowing my classes to be created publicly:
internal static class MyClassFactory
{
internal static T Create<T>(string args) where T : IMyType, new()
{
IMyType newThing = new T();
newThing.Initialise(string args);
return (T)new开发者_JAVA技巧Thing;
}
}
public interface IMyType
{
void Initialise(string args);
}
public class ThingA: IMyType
{
public void Initialise(string args)
{
// do something with args
}
}
Any help greatly appreciated :)
It looks like you are trying to roll your own service locator.
Have you considered taking the dependency injection (DI) approach? as there are reasons why you may want to avoid a service locator.
I highly recommend you take a look at some of the popular IOC containers that can perform the kind of functionality you are trying to build. Looking back, i am very glad i chose DI over service locator.
-Ninject
-Autofac
-Unity
There is something you can do, but it's really ugly...
public class ThingA: IMyType
{
[Obsolete("This constructor must not be called directly", true)]
public ThingA()
{
}
public void Initialise(string args)
{
// do something with args
}
}
This will cause a compile error if you try to call the constructor explicitly, but won't prevent calling it in the generic method with the new()
constraint.
Consider a reflection approach.
Just mark classes' constructors as private so these can't be instantiated in a regular way and your factory will call this private class constructor by using reflection.
Reflection has an impact in performance, but invoking this constructor isn't a large reflection operation.
Check this MSDN article in order to learn more about how to invoke a private constructor by using reflection:
- http://msdn.microsoft.com/en-us/library/0h6w8akb.aspx
But it can be summarized with this code snippet:
typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Private, Type.EmptyTypes, Type.DefaultBinder, null).Invoke(null);
UPDATE
By the way, I believe impacting performance and increasing code complexity in any way because you don't want developers to instantiate a class doing so only by using such factory method isn't a strong reason.
Sometimes a good developer guildelines are better than any code constraint. I say that, because, in your case, I'd implement that factory method with a T generic parameter and the same generic constraint and I if my documentation papers say "if you want an instance of T type you need to use this factory" and some decides to not follow this rule, it'd not be my responsibility and any trouble with that would be answered with "the manual says you need to use the factory".
Good habits are better than an extra-defensive code to handle human decisions.
What you could do is make a (hidden) convention that all objects implementing your interface also has a certain constructor that the factory can call. This is how ISerializable works. The drawback is that the existence of the constructor is not checked by the compiler. In your factory, find the constructor via reflection and call with the correct arguments. The constructor can be protected.
// Get constr with string arg
var constr = theType.GetConstructor(new[]{typeof(String)});
T result = (T)constr.Invoke(new[]{"argString"});
This could work for you -- using reflection instead of the new() constraint. Also notice that the constructor is private and you need to add a method to the derived class that returns an instance of itself (static):
internal static class MyClassFactory
{
internal static T Create<T>(string args) where T : IMyType
{
IMyType newThing =
(T)typeof(T).GetMethod("GetInstance").Invoke(default(object), null);
newThing.Initialise(args);
return (T)newThing;
}
}
public interface IMyType
{
void Initialise(string args);
}
public class ThingA: IMyType
{
private ThingA() { }
public static IMyType GetInstance()
{
return new ThingA(); // control creation logic here
}
public void Initialise(string args)
{
// do something with args
}
}
EDIT
Just to refine this, as was pointed out you can access a private constructor via reflection, like so:
internal class MyClassFactory
{
internal static T Create<T>(string args) where T : IMyType
{
IMyType newThing = (T)typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, Type.EmptyTypes, null).Invoke(null);
newThing.Initialise(args);
return (T)newThing;
}
}
public interface IMyType
{
void Initialise(string args);
}
public class ThingA : IMyType
{
private ThingA() { }
public void Initialise(string args)
{
Console.WriteLine(args);
}
}
Define an interface IFactory<out T> with a "Create" method, and then for each class TT you want to create, define a static object that implements IFactory<TT>. You could then either pass around the IFactory<TT> object everywhere you want to create a TT, or you could create a static class FactoryHolder<T> with which you then register the appropriate factories. Note that generic classes have a separate set of static variables for every combination of generic types, and this can provide a very convenient and type-safe means of storing a static mapping between types and factory instances.
One slight caveat is that one must register a factory for every exact type one will want to find. It's possible to register a derived-type factory as being the producer of a base type (so that e.g. FactoryHolder<Car>.GetFactory() would return an IFactory<FordFocus2Door>), but unless one explicitly registers a factory of a given exact type T, FactoryHolder<T> will come up empty. For example, either an IFactory<FordFocus2Door> or an IFactory<FordFocus4Door> could be used as an IFactory<FordFocus>, but unless one of those is registered as an IFactory<FordFocus>, neither would be returned by FactoryHolder<FordFocus>.GetFactory().
精彩评论