0

前面冗长的软件架构问题

清晰度编辑:我正在尝试将包含 NodeA, NodeB, ...等类型的对象图转换为包含*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 -> 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 -> 节点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 阶梯。

对于Node -> MyNode ( ) 情况,我希望在每种类型中都有一个工厂方法Load,类似于在MyNode -> Node ( Commit) 情况下的情况。但是staticvirtual显然有点问题。我也宁愿不做我现在必须做的两个演员。

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

4

2 回答 2

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 的转换不是简单的复制属性的事情,还表明会有很多这样的事情发生,我最初的想法是这是AutoMapper的任务。

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();
于 2013-02-08T20:42:33.980 回答
0

所有的东西都像你上面提到的那样一致吗?

如果是这样,您可以构建一组使用反射的通用 convert-to convert-from 函数。

这是我的想法(这是意识流,未经验证的编译代码):

<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;
}

我将对此进行更多研究,并可能在本周末的某个时候生成工作代码。

于 2013-02-08T19:21:41.537 回答