Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading with a class hierarchy - most derived not used

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).

like image 565
Lee Avatar asked May 03 '11 14:05

Lee


2 Answers

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);
like image 113
jgauffin Avatar answered Sep 25 '22 06:09

jgauffin


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.

like image 32
seairth Avatar answered Sep 25 '22 06:09

seairth