2011-05-03 24 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