2011-05-03 34 views
6

用类层次结构超载 - 最衍生不习惯

我试图避免代码看起来像下面的问题:

If(object Is Man) 
    Return Image("Man") 
ElseIf(object Is Woman) 
    Return Image("Woman") 
Else 
    Return Image("Unknown Object") 

我想我可以通过方法的重载做到这一点,但它总是挑选最小派生类型,我假设这是因为重载是在编译时确定的(与重写不同),因此只能在以下代码中假定基类:

代码结构:

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) 

代码执行(重点班):

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

我的情景: 基本上我创建一个游戏,并希望去耦真实世界的类(如男性),从屏幕上的课程(一个人的图像)。真实世界的物体应该没有关于它在屏幕上的表示的知识,表示将需要知道真实的物体(知道该人有多老,因此绘制了多少皱纹)。如果RealObject是一个未知类型,我希望有一个回退的地方,它仍然会显示一些东西(比如一个大的红十字)。

请注意,此代码不是我正在使用的,它是一个简化的版本,可以让问题保持清晰。如果适用,我可能需要稍后添加详细信息,我希望此代码的解决方案也可以在应用程序中使用。

什么是最优雅的方式来解决这个问题? - 如果RealObject本身没有掌握应该如何表示的信息。 XNA游戏是一个概念验证,它非常强大,如果证明可行,将从2D更改为3D(可能支持低端计算机)。

+0

为什么你反对让RealObjects参考图像? – David 2011-05-03 15:29:08

+0

要与“单一责任”保持一致。有人可能会争辩说,一个物体应该知道它的外观,但这对课程没有任何好处(除非他真的很丑,然后他可能想知道..?)。 – Lee 2011-05-03 22:31:31

回答

4

使用工厂:

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

指定工厂方法:

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

并使用它:

var imageOfSomeone = imageFactory.Create(man); 
var imageOfSomeone2 = imageFactory.Create(woman); 
var imageOfSomeone3 = imageFactory.Create(mice); 

为了能够对男性返回不同的图像,你可以使用一个条件:

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

为清楚起见,你都可以更复杂的方法添加到类:

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

    } 
} 

,并分配方法

imageFactory.Assign<Man>(PersonImageBuilders.CreateMen); 
0

您可以创建一个接受你的现实世界对象作为构造函数的参数门面类(即ManFacade,WomanFacade等)

+0

我不明白门面模式会在这里起作用吗?请举个例子。 – jgauffin 2011-05-03 17:49:10

1

如果您正在使用。NET 4,请尝试以下操作:

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

通过强制转换为动态的,实际的类型将在运行时确定,那么这应该引起被称为正确的过载。

+0

在XNA游戏的背景下,这可能不是一个好主意。 – asawyer 2011-05-03 14:25:00

+1

这可能是对的。但是,.NET在内部进行了大量优化(如站点缓存)。我建议使用分析器来查看它是否是性能瓶颈。 – seairth 2011-05-03 14:44:14

+0

为什么不使用通用方法? – jgauffin 2011-05-03 17:49:54

-1

如果RealWorld的层次结构稳定,则可以使用Visitor模式。

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; } 
    } 
} 
+0

呃。访问者模式并不意味着创建对象。它是遍历它们的。 – jgauffin 2011-05-03 17:50:34

0

我相信至少派生类中被调用的原因是因为你正在做的工作在外部类。如果你使GetImage()方法成为RealObject类的虚拟成员,那么应该调用派生得最多的版本。请注意,如果需要,您可以将GetImage()委托给ArtManager。 但@ seairth的解决方案完成相同的事情,可能会少一些侵入性。

有人可能会争辩说,将GetImage()放入RealObject类违反Single Responsibility ...我认为这取决于该类的其余部分。但在我看来,RealWorld.Render不应该为每个RealObject获取图像负责。实际上,每次添加RealObject的子类时,都必须触摸ArtManager,该子类违反了打开/关闭。

+0

是的,如果GetImage是在RealObject中的,那么它可以正确使用覆盖(而不是重载),我的理由确实是单一责任的理想。 渲染被VirtualWorld调用(它只保存对RealWorld的引用)。 – Lee 2011-05-03 22:24:26