【问题标题】:Overloading with a class hierarchy - most derived not used使用类层次结构重载 - 大多数派生不使用
【发布时间】:2011-05-03 14:07:39
【问题描述】:

问题

我试图避免如下代码:

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 游戏是一个概念验证,非常重 AI,如果证明可行,将从 2D 更改为 3D(可能同时支持低端计算机)。

【问题讨论】:

  • 您为什么反对让 RealObjects 引用图像?
  • 遵守“单一责任”。有人可能会争辩说一个对象应该知道它的外观,但这对班级没有任何好处(除非他真的很丑,那么他可能想知道......?)。

标签: c# overloading class-hierarchy


【解决方案1】:

使用工厂:

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

【讨论】:

    【解决方案2】:

    如果您使用的是 .NET 4,请尝试以下操作:

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

    通过强制转换为动态,实际类型将在运行时确定,然后会导致调用正确的重载。

    【讨论】:

    • 在 XNA 游戏的上下文中,这可能不是一个好主意。
    • 这可能是真的。但是,.NET 在内部进行了大量优化(例如站点缓存)。我建议使用分析器来查看它是否是性能瓶颈。
    • 为什么不直接使用泛型方法?
    • 这不起作用,得到错误:找不到编译动态表达式所需的一种或多种类型。您是否缺少对 Microsoft.CSharp.dll 和 System.Core.dll 的引用。执行“动态 ent = entity”之类的操作是可行的,但如果您尝试将该 ent 传递给方法,则会出现相同的错误。
    【解决方案3】:

    您可以创建接受您的真实世界对象作为构造函数参数的外观类(即 ManFacade、WomanFacade 等)

    【讨论】:

    • 我看不出门面模式在这里是如何工作的?请举个例子。
    【解决方案4】:

    我相信调用最小派生类的原因是因为您在外部类中进行工作。如果您使 GetImage() 方法成为 RealObject 类的虚拟成员,则应该调用最派生的版本。请注意,如果需要,您可以将 GetImage() 委托给 ArtManager。 但是@seairth 的解决方案完成了同样的事情,并且可能会减少干扰。

    有人可能会争辩说,将 GetImage() 放在 RealObject 类中违反了单一职责……我认为这将取决于类的其余部分是什么样的。但在我看来,RealWorld.Render 不应该负责获取每个 RealObject 的图像。实际上,每次添加 RealObject 的子类时都必须触摸 ArtManager,这违反了 Open/Closed。

    【讨论】:

    • 是的,如果 GetImage 在 RealObject 中,那么它可以使用覆盖(而不是重载)正常工作,而我不这样做的原因确实是单一职责的理想。渲染由 VirtualWorld 调用(仅包含对 RealWorld 的引用)。
    【解决方案5】:

    如果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; }
        }   
    }
    

    【讨论】:

    • 嗯。访问者模式并不意味着创建对象。就是遍历它们
    猜你喜欢
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 2023-02-07
    • 1970-01-01
    • 2013-03-18
    • 1970-01-01
    • 2011-12-08
    相关资源
    最近更新 更多