使用奇怪的重复模板模式继承序列化

时间:2013-08-18 14:22:24

标签: c# design-patterns generics xml-serialization xml-deserialization

我有一个基类定义如下:

public abstract class XMLBackedObject<T> where T: XMLBackedObject<T>
{   
/// <summary>
/// Load the specified xml file and deserialize it.
/// </summary>
/// <param name='filePath'>
/// File path to load
/// </param>
public static T Load(string filePath)
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Open))
        {
            return serializer.Deserialize(stream) as T;
        }
}

/// <summary>
/// Save this instance to the specified file path
/// </summary>
/// <param name='filePath'>
/// File path to save to.
/// </param>
public void Save(string filePath)
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Create))
        {
            serializer.Serialize(stream, this);
        }
}
}

类继承如下:

Config.cs:

using UnityEngine;
using System.Collections.Generic;

    [System.Serializable]
    public class Config : XMLBackedObject<Config>
    {
        public Config()
        {
        }

        public string WordDirectoryPath;
        public string CommandDirectoryPath;
    }

Command.cs:

using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public abstract class Command : XMLBackedObject<Command>
{
    //The word that triggers this command
    public Word Word;
    //The command's target
    public List<Word> Targets;
    //Minimum number of targets for the command to be valid
    public int RequiredTargets;

    //Message to send when bad targets are supplied
    public string BadTargetString;
    //Message to send when no target is supplied
    public string noTargetString;


    public Command(Word word, List<Word> targets,int requiredTargets)
    {
        Targets = targets;
        this.Word = word;
        this.RequiredTargets = requiredTargets;
    }

    public Command()
    {
        Targets = new List<Word>(); 
    }

    /// <summary>
    /// Execute the command on the supplied targets
    /// </summary>
    /// <param name='targets'>
    /// Targets to process
    /// </param>
    public abstract void Execute(IEnumerable<Word> targets);
}

MenuNavigationCommand.cs:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class MenuChoiceCommand : Command {

    public MenuChoiceCommand()
    {
    }

    public MenuChoiceCommand(Word word, List<Word> targets, int requiredTargets) : base(word,targets,requiredTargets)
    {
    }

    public override void Execute (System.Collections.Generic.IEnumerable<Word> targets)
    {

    }
}

这是调用Save函数的代码:

public void BuildTestXMLFiles()
    {
        Config config = new Config();
        config.CommandDirectoryPath = "commdirpath";
        config.WordDirectoryPath = "wordirparth";
        config.Save (Application.dataPath + "/testconfig.xml");

        MenuChoiceCommand command = new MenuChoiceCommand(word,new List<Word>(),2);
        command.Targets.Add (word);
        command.Save (Application.dataPath + "/testcommand.xml");
    }

Config的Save函数执行没有任何障碍,但在MenuNavigationCommand上使用Save会给我这个错误:

InvalidOperationException: The type of the argument object 'MenuChoiceCommand' is not primitive.

我需要MenuNavigationCommand做的就是保存它继承的Command类中存在的字段,而不是MenuNavigationCommand中的任何新字段。有没有办法做到这一点?或者我应该在每个使用多个继承级别的类上实现Load和Save方法?

编辑:添加了文件的完整源代码。

1 个答案:

答案 0 :(得分:1)

MenuChoiceCommand继承Command,继承XMLBackedObject<Command>,而不是XMLBackedObject<MenuChoiceCommand>。因此,Save创建的序列化程序适用于Command类型,而不是MenuChoiceCommand ...您需要使MenuChoiceCommand继承XMLBackedObject<MenuChoiceCommand>才能使其生效(但是然后你将无法继承Command,因为C#不允许多重继承。)

乍看之下,使用奇怪的重复模板模式似乎是一个好主意,但正如您所看到的,您可以快速遇到其局限性。

无论如何,我不认为序列化逻辑应该是数据类本身的一部分;使用泛型方法在辅助类中执行它可能会更好:

public static class XmlHelper
{
    public static T Load<T>(string filePath)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Open))
        {
            return (T)serializer.Deserialize(stream);
        }
    }

    public static void Save<T>(T obj, string filePath)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        using(FileStream stream = new FileStream(filePath, FileMode.Create))
        {
            serializer.Serialize(stream, obj);
        }
    }
}