在多态对象图之间转换

时间:2013-02-08 19:07:59

标签: c# architecture

提前冗长的软件架构问题

CLARITY EDIT :我正在尝试转换包含类似类型的对象图 NodeANodeB,...到包含*My*NodeA*My*NodeB等类型的对象图,反之亦然。 NodeX类型中的属性对应于MyNodeX类型中的类似属性,但在许多情况下,只是一个简单的赋值。

如果我有两个类似的类结构:

// pure model, i.e. minimal information that is convenient for storage

abstract class Node
{
    public int BaseProperty { get; set; }
    public NodeCollection Children { get; private set; } // : Collection<Node>
}

class NodeA /* NodeB, NodeC ... */ : Node
{
    public int DerivedAProperty { get; set; }
}

// objects that are convenient for being used by the application

abstract class MyNode
{
    public int MyBaseProperty { get; set; }
    public MyNodeCollection Children { get; private set; } // : Collection<MyNode>
}

class MyNodeA /* MyNodeB, MyNodeC ... */ : MyNode
{
    public int MyDerivedAProperty { get; set; }
}

,我需要将NodeX类型的对象图转换为MyNodeX类型之一,反之亦然,而无需更改任何NodeX类所有,我发现自己经常使用这种模式:

NodeX - &gt; MyNodeX

// USAGE / external code
Node node = ...
MyNode myNode = MyNode.Load(node, ARGS); // static factory
abstract class MyNode
{
    ...
    // factory
    public static MyNode Load(Node node, ARGS)
    {
        var type = node.GetType();
        MyNode myNode;
        // no 'is' usage because NodeB could be derived from NodeC etc.
        if (type == typeof(NodeA))
            myNode = new MyNodeA(ARGS); // arbitrary ctor
        else if (...)
            ...
        myNode.Load(Node);
        return myNode
    }
    public virtual void Load(Node node)
    {
        this.MyBaseProperty = node.BaseProperty;
        foreach (var child in node.Children)
            this.Children.Add(MyNode.Load(child, this.ARGS));
    }
}
class MyNodeA : MyNode
{
    ...
    public override void Load(Node node)
    {
        var m = (NodeA)node; // provoke InvalidCastException if coding error
        base.Load(node);
        this.MyDerivedAProperty = m.DerivedAProperty;
    }
}

MyNodeX - &gt;节点X

// USAGE / external code
MyNode myNode = ...
Node node = myNode.Commit();
abstract class MyNode
{
    ...
    // 'kind of' factory
    public abstract Node Commit();
    public virtual Commit(Node node)
    {
        node.BaseProperty = this.MyBaseProperty;
        foreach (var child in this.Children)
            node.Children.Add(child.Commit());
    }
}
class MyNodeA : MyNode
{
    ...
    public override Node Commit()
    {
        var m = new NodeA(); // "factory" method for each type
        this.Commit(m);
        return m;
    }
    public override void Commit(Node node)
    {
        var m = (NodeA)node; // provoke InvalidCastException if coding error
        base.Commit(node);
        m.DerivedAProperty = this.MyDerivedAProperty;
    }
}

我已成功多次使用此方法,我一般都喜欢它,因为必须添加到类中的方法是直接的,外部代码也是如此。此外,它还可以通过调用base.Load(node) / base.Commit(node)来避免代码重复。但是,我真的不喜欢静态Load工厂方法中的if / else梯形图。

我希望节点的每种类型都有一个工厂方法 - &gt; MyNode Load)情况,类似于 MyNode - &gt;中的情况节点Commit)案例。但staticvirtual显然有点问题。我也不愿意做我现在必须做的两个演员。

以某种方式实现这样的事情吗?

2 个答案:

答案 0 :(得分:1)

我的建议是逐步解决问题。首先,您需要一些东西来遍历树并沿途转换每个节点:

public static class NodeExtensions
{
    public static MyNode ToMy( this Node node ) 
    {
        var result = node.Transform();
        result.Children = node.Children.Select( ToMy ).ToList();
    }

    public static Node FromMy( this MyNode node ) 
    {
        var result = node.Transform();
        result.Children = node.Children.Select( ToMy ).ToList();
    }

    public static MyNode Transform( this Node node )
    {
        // TODO code to transform any single node here
    }

    public static Node Transform( this MyNode node )
    {
        // TODO code to transform any single node here
    }
}

既然你提到从Node到MyNode的转换不是复制属性的简单问题,而且还表明会有很多这种情况发生,我最初的想法是这是{{3}的任务}。

AutoMapper允许您创建“转换配置文件”,该配置文件描述要映射的属性以及要应用于任何给定映射的任何特殊规则。此外,它提供了通用和非泛型方法,因此即使您在编译时不知道类型,也可以使用它。它通常用于在实体和视图模型之间进行转换,因此您可以在其他地方找到与其使用相关的大量问题和答案。

定义类型映射基本上包含许多这样的调用:

Mapper.CreateMap<Node,MyNode>(); // no special rules for this map

您必须使用AutoMapper来获取有关如何创建特殊映射的详细信息,例如拆分属性或执行类型转换。您还需要双向创建地图,以便能够在任一方向上进行映射。

一旦定义了所有映射,Transform扩展方法就可以这么简单:

    public static MyNode Transform( this Node node )
    {
        return Mapper.Map( node.GetType(), node.GetMatchingMyType(), node );
    }

    public static Type GetMatchingType( this Node node )
    {
        // you can use a dictionary lookup or some other logic if this doesn't work
        var typeName = "My" + node.GetType().Name;
        return typeof(MyNode).Assembly.GetTypes().Single( t => t.Name == typeName );
    }

当一切就绪后,您可以通过写下来转换整个树:

var myTree = node.ToMy();
// and back
node = myTree.FromMy();

答案 1 :(得分:0)

上面提到的所有内容是否一致?

如果是这样,你可以构建一组使用反射的泛型转换为转换函数。

这就是我的想法(这是一种意识流,而不是经过验证的编译代码):

<T> ConvertTo<TMy, T>(TMy object)
{
  // create an object of type T
  T newObj = new T();


  // iterate the members of T using reflection
  foreach(member in T)
  {
    // find the equavalent My members in TMy
    // transfer the data
  }
  return newObj;
}

我会更多地研究这个问题,并可能在本周末的某个时候生成工作代码。