The problem
I am trying to avoid code that looks like the following:
If(object Is Man)
Return Image("Man")
ElseIf(object Is Woman)
Return Image("Woman")
Else
Return Image("Unknown Object")
I thought I could do this through method overloading, but it always picks the least derived type, I assume this is because the overloading is determined at compile time (unlike overriding), and therefore only the base class can be assumed in the following code:
Code structure:
NS:Real
RealWorld (Contains a collection of all the RealObjects)
RealObject
Person
Man
Woman
NS:Virtual
VirtualWorld (Holds a reference to the RealWorld, and is responsible for rendering)
Image (The actual representation of the RealWorldObject, could also be a mesh..)
ArtManager (Decides how an object is to be represented)
Code Implementation (key classes):
class VirtualWorld
{
private RealWorld _world;
public VirtualWorld(RealWorld world)
{
_world = world;
}
public void Render()
{
foreach (RealObject o in _world.Objects)
{
Image img = ArtManager.GetImageForObject(o);
img.Render();
}
}
}
static class ArtManager
{
public static Image GetImageForObject(RealObject obj)// This is always used
{
Image img = new Image("Unknown object");
return img;
}
public static Image GetImageForObject(Man man)
{
if(man.Age < 18)
return new Image("Image of Boy");
else
return new Image("Image of Man");
}
public static Image GetImageForObject(Woman woman)
{
if (woman.Age < 70)
return new Image("Image of Woman");
else
return new Image("Image of Granny");
}
}
My scenario: Essentially I am creating a game, and want to decouple real-world classes (such as a man), from on-screen classes (an image of a person). The real world object should have no knowledge of it's on-screen representation, the representation will need to be aware of the real object (to know how old the man is, and therefore how many wrinkles are drawn). I want to have the fallback where if a RealObject is of an unknown type, it still displays something (like a big red cross).
Please note that this code is not what i'm using, it's a simplified version to keep the question clear. I may need to add details later if applicable, I'm hoping the solution to this code will also work in the application.
What's the most elegant way to solve this? - Without the RealObject itself holding information on how it should be represented. The XNA game is a proof of concept which is very AI heavy, and if it proves doable, will be changed from 2D to 3D (probably supporting both for lower end computers).
Use a factory:
public class ImageFactory
{
Dictionary<Type, Func<IPerson, Image>> _creators;
void Assign<TPerson>(Func<IPerson, Image> imageCreator) where T : IPerson
{
_creators.Add(typeof(TPerson), imageCreator);
}
void Create(Person person)
{
Func<IPerson, Image> creator;
if (!_creators.TryGetValue(person.GetType(), out creator))
return null;
return creator(person);
}
}
Assign factory methods:
imageFactory.Assign<Man>(person => new Image("Man");
imageFactory.Assign<Woman>(person => new Image("Big bad mommy");
imageFactory.Assign<Mice>(person => new Image("Tiny little mouse");
And use it:
var imageOfSomeone = imageFactory.Create(man);
var imageOfSomeone2 = imageFactory.Create(woman);
var imageOfSomeone3 = imageFactory.Create(mice);
To be able to return different images for men you can use a condition:
factory.Assign<Man>(person => person.Age > 10 ? new Image("Man") : new Image("Boy"));
For clarity you can add all more complex methods to a class:
public static class PersonImageBuilders
{
public static Image CreateMen(IPerson person)
{
if (person.Age > 60)
return new Image("Old and gready!");
else
return new Image("Young and foolish!");
}
}
And assign the method
imageFactory.Assign<Man>(PersonImageBuilders.CreateMen);
If you are using .NET 4, try the following:
Image img = ArtManager.GetImageForObject((dynamic)o);
By casting to dynamic, the actual type will be determined at runtime, which should then cause the correct overload to be called.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With