2011-10-10 61 views
1

我覺得我知道設計模式,但這是逃避我。我有兩個單獨的項目,一個作爲另一個的庫。該庫讀取XML文件並將其解析爲數據結構。它只是負責轉換和轉換XML。我的另一個項目是一個引擎,它對庫中組裝的數據起作用。它可能包含映射庫中的類的類,但是包含行爲方法。如果你想知道,引擎和庫分離的原因是有第三個項目,一個編輯器,修改XML數據。遊戲而已。抽象描述與實現之間的對象

我現在正在庫中創建一個類,它代表可以在引擎中執行的一組命令,以及包含許多不同命令的對象。我的問題是我找不出一個好的模式來定義這些命令和抽象它們的容器,這樣引擎就不必切換類型。如果這裏沒有兩個項目,只有一個,這很容易。不同的命令會實現一個包含一個Execute()方法或類似的東西的接口。但它不會在這裏工作。該庫沒有實現這些命令的行爲的能力,只是它們從XML中提取的屬性。

我可以在庫中創建容器可以使用的接口,其中包含加載和保存XML數據的方法。但發動機仍然將不得不switch上的命令,以兩種:

  1. 運行中發動機的容器類的方法來相應地修改自己的狀態,或
  2. 創建正確類型引擎對象控制的該命令的行爲,然後通過其接口執行該命令。

有問題的容器是我的遊戲及其關鍵幀的過場動畫。如音樂,圖像,文字等

下面的命令將控制其行爲,是從圖書館借了一些示例代碼:

public class SceneInfo 
{ 
    /* Other stuff... */ 
    public List<KeyFrameInfo> KeyFrames { get; private set; } 
} 

public class KeyFrameInfo 
{ 
    public List<IKeyFrameCommandInfo> Commands { get; private set; } 
} 

public class KeyFramePlayCommandInfo : IKeyFrameCommandInfo 
{ 
    public int Track { get; set; } 

    public static KeyFramePlayCommandInfo FromXml(XElement node) 
    { 
     var info = new KeyFramePlayCommandInfo(); 
     info.Track = node.GetInteger("track"); 
     return info; 
    } 

    public void Save(XmlTextWriter writer) 
    { 
     writer.WriteStartElement("PlayMusic"); 
     writer.WriteAttributeString("track", Track.ToString()); 
     writer.WriteEndElement(); 
    } 
} 

我沒有寫發動機一半,而且,但它會訪問keyframe.Commands,遍歷它,並做...某事。我試圖避免一個類型切換,沒有過度工程這個問題。它可能有類似KeyFramePlayCommand(與KeyFramePlayCommandInfo比較)。

解決此問題的任何良好模式?

+0

Post IKeyFrameCommandInfo :) –

+0

它只有Save方法,但它並不真正相關,我想。如果你需要修改它的答案,請這樣做。 – Tesserex

回答

0

您可以爲工廠創建一個接口,以創建給定命令類型的命令執行程序。還要添加一個函數來將工廠註冊到您的庫中。當你需要執行一個命令時,你可以給註冊工廠的命令,並獲得一個執行器對象。如果我正確地理解了你的問題,這段代碼就在庫的一邊。

現在,從您的應用程序端,您可以創建一個工廠實現,將所有已知命令類型註冊爲可執行它們的類,最後將此工廠註冊到庫。


有很多不同的方法可以做到這一點。我會添加假設你不想添加void Execute()SceneInfo,KeyFrameInfoIKeyFrameCommandInfo - 畢竟他們是信息類。所以,讓我們創建一個SceneRunner類:

public class SceneRunner 
{ 
    public ExecuteScene(SceneInfo scene) { 
     // loop over scene.KeyFrames and keyFrames.Commands, and execute 
    } 
} 

因爲我們不知道如何執行這些命令,讓我們創建一個工廠,使我們能夠獲得一個知道如何做到這一點的一類:

public interface IKeyFrameCommandFactory 
{ 
    IKeyFrameCommand GetCommand(IKeyFrameCommandInfo info); 
} 

並添加工廠界面和註冊機制到跑步者。由於應用面可能不會想對付這種情況下,讓我們做靜態:

public class SceneRunner 
{ 
    static public RegisterFactory(IKeyFrameCommandFactory factory) 
    { 
     this.factory = factory; 
    } 

    static private IKeyFrameCommandFactory factory = null; 
} 

到目前爲止,一切都很好。現在,工廠代碼(庫客戶端)的時間。這非常類似於Daryl suggested(你可以使用自己的代碼逐字,僅僅通過增加接口規範):

public void KeyCommandFactory: IKeyFrameCommandFactory 
{ 
    private static Map<Type, Type> mappings; 

    public IKeyCommand GetKeyCommand(IKeyCommandInfo info) 
    { 
     Type infoType = info.GetType(); 
     return Activator.CreateInstance(mappings[infoType], info); 
    } 
} 

如果你不喜歡進入反射,你可以實現你的命令Prototype Pattern,並使用IDictionary<Type, IPrototype> mappings;作爲您的地圖,並使用mappings[infoType].Clone()來獲取命令的新實例。

現在,還剩下兩件事:將信息類與命令類關聯起來,並註冊工廠。兩者都應該相當明顯。

對於關聯,您可以將RegisterCommand(Type infoType, IPrototype command)添加到您的工廠,並將關聯添加到程序入口點,或者將它們關聯放在靜態方法中,然後從程序入口點調用該方法。你甚至可以設計一個屬性來指定命令類的信息類,解析你的程序集並自動添加關聯。

最後,通過調用SceneRunner.RegisterFactory(new KeyCommandFactory())將您的工廠註冊到您的SceneRunner中。

+0

你可以擴展一些示例代碼嗎?我不太明白一切。 – Tesserex

0

對於處理實例化的問題(特別是抽象遠離邏輯的實例化)工廠通常會發揮作用。

public void KeyCommandFactory{ 
    public static GetKeyCommand(IKeyCommandInfo info){ 
     /* ? */ 
    } 
} 

因爲你只是公開一個接口,我們還有另一個問題:實例化哪個類?

我現在能想到的最好方法是鍵/類型映射。

public void KeyCommandFactory{ 
    private static Map<string,Type> Mappings; 

    private static KeyCommandFactory(){ 
     /* Example */ 
     Mappings.Add("PlayMusic",typeof(PlayMusicCommand)); 
     Mappings.Add("AnimateFrame",typeof(AnimateFrameCommand)); 
     Mappings.Add("StopFrame",typeof(StopFrameCommand)); 
    } 

    public static GetKeyCommand(IKeyCommandInfo info){ 
     return (IKeyCommandInfo)Activator.CreateInstance(Mappings[info.CommandName]); // Add this property 
    } 
} 

如果你願意堅持公約,你甚至可以嘗試使用反射,以便命令名稱您的命令對象的名稱相匹配,但是這當然是不夠靈活。

public void KeyCommandFactory{ 
    public static GetKeyCommand(IKeyCommandInfo info){ 
     Type type = Type.GetType("Namespace.To." + info.CommandName + "Command"); 
     return (IKeyCommandInfo)Activator.CreateInstance(type, info); // Add this property 
    } 
} 

我會把你的圖書館之外(在引擎中)。在上面,我將info實例作爲參數傳遞給您的命令的新實例。

+0

好的,所以你的確使用了某種切換方式,但它被包裝成一個映射。但我想沒關係,我已經在引擎的其他地方使用了Activator.CreateInstance。 – Tesserex

+0

@Tesserex我想,必須在某處映射某種映射才能擺脫類型切換。我還沒有想出任何其他方式來實現這一點。 –