 |
|
 |
There is a way to simulate an factory where needs to pass arguments for constructor to create the object. Look the code
//INTERFACE
public interface IObjectInterface
{
void SetConstructorParameter(ParameterType args);
}
//Generic Object Factory Class
public class ObjectFactory
{
//Register a object with the factory
public void Register(KeyType key)
where SpecificClass : GeneralClass,IObjectInterface, new()
{
if (m_mapProducts == null)
{
m_mapProducts = new SortedList();
}
CreateFunctor createFunctor = Creator;
m_mapProducts.Add(key, createFunctor);
}
//Create a registered object
public GeneralClass Create(KeyType key, ArgumentType value)
{
CreateFunctor createFunctor = m_mapProducts[key];
return createFunctor(value);
}
private GeneralClass Creator(ArgumentType value)
where SpecificClass : GeneralClass, IObjectInterface , new()
{
SpecificClass specificProduct = new SpecificClass();
specificProduct.SetConstructorParameter(value);
return specificProduct;
}
private delegate GeneralClass CreateFunctor(ArgumentType value);
private SortedList m_mapProducts;
}
// USAGE EXAMPLE
public abstract class Fruit : IObjectInterface
{
public string Value;
public abstract void SetConstructorParameter(string args);
}
public class Apple : Fruit
{
public override void SetConstructorParameter(string args)
{
base.Value = (string)args;
}
}
class Orange : Fruit
{
public override void SetConstructorParameter(string args)
{
base.Value = (string)args;
}
}
class Program
{
static void Main(string[] args)
{
ObjectFactory factory = new ObjectFactory();
//Register
factory.Register("Apple");
factory.Register("Orange");
//Create
Fruit fruit1 = factory.Create("Apple", "maca");
Fruit fruit2 = factory.Create("Orange", "laranja");
Console.WriteLine(fruit1.Value);
Console.WriteLine(fruit2.Value);
}
}
|
|
|
|
 |
|
 |
"Whenever you add a new object type, the factory has to be updated"
But are you still avoiding having to update your code? Each time you create a new class you are still going to have to update the code to call the Register method:
//Register
factory.Register< Apple >( "Apple" );
factory.Register< Orange>( "Orange" );
I would agree with the other poster's suggestion to utilize reflection or possible this could be externalized within some kind of XML Config file.
thx
ST
|
|
|
|
 |
|
 |
Yes, there is some minimal effort to get an object registered but there is no change to the object factory. The example code is simplification of reality. In a real program you'll probably use the factory in a singleton pattern and let the objects do some form of self-registration.
I don't think it is possible to use reflection and keep the code type safe. I'm starting to get the impression that type safety is just not a big issue with C# developers.
If there is anyone out there who can show me a type safe object factory using reflection then I'll remove this article.;)
|
|
|
|
 |
|
 |
Rob
Try this article:
Harness the Power of Reflection: Creating an Object Factory
http://www.eggheadcafe.com/articles/20050717.asp
BTW -- I still like your article and I was not suggesting nor trying to get you to remove it. I was just offering a different view point. Sorry it if came across the wrong way.
|
|
|
|
 |
|
 |
What do you mean "type safety" is not a big issue? C# is extremely strict in terms of type checking. Concerning reflection, as I said in another posting, the new() generic constraint is actually implemented through reflection (the whole concept of generics is actually built on reflection). Of course, in terms of syntax, generics are much more readable, but that's all there is to it.
|
|
|
|
 |
|
 |
I agree C# is type strict but it can still be used in an unsafe manner. Using generics instead of reflection ( or using reflection by means of generics ) will ensures compile time type checking. In other words when it compiles it will never throw an exception at run time.
So I still think it is a bit more then just nice syntax…
|
|
|
|
 |
|
 |
Ok, you're right about that, using generics will enforce produced objects to inherit a specific general object at runtime, while with types you have to explicitly do the check runtime. But otherwise, generics still work using reflection underneath.
|
|
|
|
 |
|
 |
Hi Rob,
just wanted to say that I like your article and example becuase its simple and illustrates the point without lot's of bla bla. I could'nt agree with you more concerning your views on type safety and reflection!
I could as someone here already mentioned, imagine a real-world example combining this with the singleton pattern for factory access and the ability to read in the types from a config-file for type registration.
I have one question, regarding this pattern:
1. How do you handle constructor arguments? Here you assume that all of the concrete types are creatable, e.g. have constructors, without parameters. However in practice this is almost alwasys not the case and the client code that calls create, can't supply that info without breaking the pattern...
Thanx
Chris
|
|
|
|
 |
|
 |
Hi Chris,
Finally someone who can appreciate some good programming practice.
I've been working on those constructor arguments. Using C++ that would not be a problem. But when using C# there seems to be no way to bypass CS0417. Why did they think up that compile error in the first place? For now factory products must be default constructible. But I'm not giving up yet. Maybe a C# equivalence of a boost::bind like method can solve this. If anyone has suggestions please let me know.
To be continued...
|
|
|
|
 |
|
 |
I've used reflection as followings:
(VB.NET) GetType(T).GetConstructor(System.Type.EmptyTypes).Invoke(Nothing)
It's true that it's not type safe because it suppose to have a default constructor with no parameters.
|
|
|
|
 |
|
 |
Got my five, however, as leppie said, it's more convenient to use reflection to do this in C#.
|
|
|
|
 |
|
 |
Like it a lot.
Also,reflection has overhead which this approach avoids.
Matthew
|
|
|
|
 |
|
 |
Actually, the new() generic constraint IS implemented through reflection. You can verify this by throwing an exception inside your constructor and checking the call stack. Thus, reflection or not it is exactly the same in terms of performance and type safety. The only difference is in syntax (I prefer the generic syntax as well, but it's only a matter of style).
|
|
|
|
 |
|
|
 |
|
 |
I don't have much C# experience, so correct me if I'm wrong, but I think :
The Activator.CreateInstance<T>() can only be used when the type argument is known at compile time. The factory code is intended for situations where this is only known at run time. You could use Activator.CreateInstance( System.Type ) but then you will lose type safety. The factory code is type safe.
In fact, is there any difference between :
Foo foo = CreateInstance<Foo>();
and
Foo foo = new Foo();
|
|
|
|
 |
|
 |
Why in the world do you need this "type safety", as you call it, anyway? Casts throw an exception if the object is not of the correct type. Thus, simply use what amounts to:
(MyBaseType)(typeof(MyRealType).GetConstructor(new Type[]{}).Invoke(new object[]{}));
|
|
|
|
 |
|
 |
Becuase errors at compile time are always better than run-time errors since:
1. they're easier to find and understand
2. such static type checkable code usually runs faster, e.g. no run-time casting, type checking needed
3. such code is usually robuster and more deterministic
Of course at the cost of flexability....
|
|
|
|
 |
|
 |
Not really worth the tradeoffs IMHO.
"such code is usually robuster and more deterministic"
So it is "robuster" to have to register each type you're going to use when you could simply discover it by name via the reflection API and not have to worry about registration--or even have to resort to using features in C# 2.0, which some people may not wish to use (Or in my case, which are forbidden to use by my lame instructor)?
|
|
|
|
 |
|
 |
Yes it is, since you have full type safety in this case at "compile time". That's the difference here. Anyways, if you "discover" the objects interfaces at run-time, what are the chances that you can do something meaningfull with them, e.g. you client-creator must make some assumptions here and given that, why not use the compiler to verify them?
Chris
|
|
|
|
 |