Question

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

Was it helpful?

Solution

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

OTHER TIPS

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.

You could create Facade classes that accept your real-world object as a constructor argument (ie ManFacade, WomanFacade, etc.)

I believe the reason the least derived class is being called is because you're doing the work in an external class. If you make the GetImage() method a virtual member of the RealObject class then the most-derived version should get called. Note that you can have GetImage() delegate to the ArtManager if you want. But @seairth's solution accomplishes the same thing and would probably be less intrusive.

One could argue that putting GetImage() in the RealObject class violates Single Responsibility ... I think that would depend on what the rest of the class looks like. But it seems to me that RealWorld.Render shouldn't be responsible for obtaining the images for each RealObject. And as it is, you'd have to touch ArtManager each time you add a subclass of RealObject, which violates Open/Closed.

If the hiearchy of the RealWorld is stable you could use the Visitor pattern.

public abstract class RealObject
{
    public abstract void Accept(RealObjectVisitor visitor);
}

public class Man : RealObject
{
    public override void Accept(RealObjectVisitor visitor)
    {
        visitor.VisitMan(this);
    }
}

public class Woman : RealObject
{
    public override void Accept(RealObjectVisitor visitor)
    {
        visitor.VisitWoman(this);
    }
}

public abstract class RealObjectVistor
{
    public abstract void VisitMan(Man man);
    public abstract void VisitWoman(Woman woman);        
}


public class VirtualObjectFactory
{
    public VirtualObject Create(RealObject realObject)
    {
        Visitor visitor = new Visitor();
        realObject.Accept(visitor);
        return visitor.VirtualObject;
    }  

    private class Visitor : RealObjectVistor
    {  
        public override void VisitMan(Man man)
        {
            VirtualObject = new ManVirtualObject(man);
        }

        public override void VisitWoman(Woman woman)
        {
            VirtualObject = new WomanVirtualObject(woman);
        }

        public VirtualObject VirtualObject { get; private set; }
    }   
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top