工厂和 C#

Factories and C#

我正在编写一个程序,其界面类似于 Windows Explorer。在此资源管理器视图中,可以有各种类型的 object,但每个 object 都将实现相同的接口,因此资源管理器和程序的其他部分可以以相同的方式处理它们那将对他们起作用。所以假设我有接口:

public interface IMyObject
{
    IMyObject Parent { get; }
    string DisplayName { get; }
    bool IsParent { get; }
}

出于显而易见的原因,我不想在我的应用程序中设置这些属性。它们都应该通过 object 的构造函数设置。所以 object 看起来像:

public class MyFileObject : IMyObject
{
    public IMyObject Parent { get; protected set; }
    public string DisplayName { get; protected set; }
    bool IsParent { get; protected set; }

    public MyFileObject(IMyObject parent, string displayName)
    {
        Parent = parent;
        DisplayName = displayName;
        IsParent = false;
    }
}

在处理的某个时刻,这些 object 被序列化为 XML 文件并保存到磁盘。 object 的类型保存为 XML 属性。为了避免重复一堆代码,我首先想到创建一个 object 工厂,它可以从提供的 XML 中创建这些 object。但是,由于在解析 XML 之前我不知道我有什么类型的 object,所以我不能只在工厂中执行 "new MyFileObject(...)"。我可以使用反射从 XML 创建 object 的类型,但这很慢。然后我遇到了设置受保护属性的问题。再次,反思,再次,缓慢。

所以我的问题是,如何最好地使用提供的 XML 创建这些 object,并且希望不必创建我的所有属性 get/set?我正在使用 Castle Windsor 并简要地查看了它的工厂实现,但无法找到在这种情况下使其工作的方法。

编辑:昨天晚些时候我才意识到我没有给你全貌。出现问题的地方是,有时我不得不从 Xml 创建 object,而其他时候我不得不创建它们 "on the fly",因为它们是在机器并添加到 parent。我试图想出一个工厂来提供从任一来源创建 object 的标准方法。理查德诺德的回答看起来仍然是我正在做的最好的。我不太了解 IL 部分,但 Linq 表达式看起来很有前途。

这里有 2 种可能表现良好的方法。

  1. 使用 IL emit 构造您的对象:http://ayende.com/blog/3167/creating-objects-perf-implications。 这将需要在您的工厂中为每个具体类型创建和编译一个构造函数委托,然后使用 XML 的 type 属性(参见下面的示例)来选择要调用的委托。

  2. 使用已编译的 lambda 表达式:http://rogeralsing.com/2008/02/28/linq-expressions-creating-objects/。同样在这里,一个按具体类型编译的表达式并根据类型名称调用适当的表达式。

附带说明一下,您在序列化时必须考虑父 属性。就像现在一样,Parent 属性 将为其每个子项保存到 XML 一次,这可能不是最好的方法。

或者,您可以使用子集合并将其序列化为嵌套对象。这将允许您在集合中添加子项而无需将任何 IMyObject 实现作为构造函数参数传递,并且如果 Parent 属性 是必需的,则在从中添加或删除子项时,只需将 Parent 设置为要求的价值。

可选地,创建一个实现 ICollection 的抽象 class 意味着您不必在每个具体类型中重复练习。

public interface IMyObject : ICollection<IMyObject>
{
    string DisplayName { get; }

    IMyObject Parent { get; }
}

public class MyFileObject : MyAbstractFileObject 
{
    public MyFileObject(string displayName)
        : base(displayName) 
    {
    }
}

public abstract class MyAbstractFileObject : IMyObject
{
    private ICollection<IMyObject> children;

    protected MyAbstractFileObject(string displayName)
    {
        this.DisplayName = displayName;
        this.children = new Collection<IMyObject>();
    }

    public IMyObject Parent { get; protected set; }

    public string DisplayName { get; protected set; }

    public void Add(IMyObject child)
    {
        this.children.Add(child);
        child.Parent = this;
    }

    public void Remove(IMyChild child)
    {
        this.children.Remove(child);
        child.Parent = null;
    }

    // other ICollection<IMyObject> members
}

将对象树序列化为:

<Object type="Namespace.MyFileObject" displayName="...">
    <Object type="Namespace.MyFileObject" displayName="...">
        <Object type="Namespace.MyFileObject" displayName="..." />
        <Object type="Namespace.MyOtherFileObject" displayName="..." />
    </Object>
    <Object type="Namespace.MyOtherFileObject" displayName="..." />
</Object>

虽然不是很简洁的关于对象构造的回复,希望对大家有所帮助。

我建议您引入 IMyObjectReader 接口并将每个 IMyObject 实现与相应的 reader 配对。 一些演示该方法的代码:

interface IMyObject
{
   // common properties omitted
}

interface IMyObjectReader
{
    IMyObject Parse(XElement xml, IMyObject parent);
}

public class MyFileObject:IMyObject
{
    // common properties omitted
    public string Property1 { get; set; }
}

public class MyPictureObject:IMyObject
{
    // common properties omitted
    public string Property2 { get; set; }
}

class MyFileObjectReader : IMyObjectReader
{
    public IMyObject Parse(XElement xml, IMyObject parent)
    {
        var displayName = xml.Attribute("DisplayName").Value;
        return new MyFileObject(parent,displayName)
        {
            Property1 = xml.Attribute("Property1").Value
        };
    }
}

class MyPictureObjectReader : IMyObjectReader
{
    public IMyObject Parse(XElement xml, IMyObject parent)
    {
        return new MyPictureObject(parent)
        {
            Property2 = xml.Attribute("Property2").Value
        };
    }
}

class MyObjectGraphXmlReader 
{
    Dictionary<string, IMyObjectReader> readerMap;
    public MyObjectGraphXmlReader()
    {
        readerMap = new Dictionary<string, IMyObjectReader>
        {
            {"File", new MyFileObjectReader()},
            {"Picture", new MyFileObjectReader()},
            //etc
        };
    }

    public IEnumerable<IMyObject> ReadMyObjects(XElement xmlRoot) 
    {
        return xmlRoot.Elements()
                .Select((e)=>ParseElement(e,null));
    }
    private IMyObject ParseElement(XElement element, IMyObject parent)
    {
        string readerKey = element.Attribute("Type").Value; 
        var reader = readerMap[readerKey];
        var myObject = reader.Parse(element, parent);
        foreach (var subElement in element.Elements())
        {
            ParseElement(subElement, parent);
        }
        return myObject;
    }
}