Overload with class hierarchy - most received are not used

Problem

I am trying to avoid code that looks like this:

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 with the method overload, but it always selects the least derived type, I assume that this is because the overload is determined at compile time (as opposed to overriding), and therefore only the base class can be accepted in 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: In essence, I create a game and want to separate the classes of the real world (for example, a person) from the classes on the screen (image of a person). An object of the real world should not know about this screen representation, the idea should know about the real object (know how old the man is, and therefore how many wrinkles are drawn). I want to have a backup, where if RealObject is of an unknown type, it still displays something (like a big red cross).

Please note that this code is not what I use, it is a simplified version to keep the question clear. I may need to add details later, if applicable. I hope that the solution to this code will also work in the application.

What is the most elegant way to solve this problem? - Without RealObject itself, containing information on how it should be presented. The XNA game is a proof of concept, which is very difficult, and if it turns out to be feasible, it will be changed from 2D to 3D (perhaps supported as for lower computers).

+6
source share
5 answers

Use 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 the condition:

 factory.Assign<Man>(person => person.Age > 10 ? new Image("Man") : new Image("Boy")); 

For clarity, you can add more complex methods to the 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 a method

 imageFactory.Assign<Man>(PersonImageBuilders.CreateMen); 
+4
source

If you are using .NET 4, try the following:

 Image img = ArtManager.GetImageForObject((dynamic)o); 

Discarding the dynamics, the actual type will be determined at runtime, which then should cause the correct overload.

+1
source

You can create Facade classes that take your real-world object as a constructor argument (e.g. ManFacade, WomanFacade, etc.).

0
source

I believe the reason for the least derived class is because you are doing work in an outer class. If you make the GetImage () method a virtual member of the RealObject class, then the most derived version is called. Please note that you can have a GetImage () delegate in ArtManager if you wish. But @seairth's solution does the same, and is likely to be less intrusive.

It could be argued that including GetImage () in the RealObject class violates a single responsibility ... I think it will depend on how the rest of the class looks. But it seems to me that RealWorld.Render should not be responsible for obtaining images for each RealObject. And be that as it may, you will have to touch ArtManager every time you add a subclass of RealObject that breaks Open / Closed.

0
source

If hiearchy RealWorld is stable, you can use the Visitor template.

 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; } } } 
-1
source

Source: https://habr.com/ru/post/887202/


All Articles