0

長いソフトウェアアーキテクチャの質問

明確さの編集:、、 ...などのタイプで構成されるオブジェクトグラフを、、...などNodeAのタイプで構成されるオブジェクトグラフに変換しようとしています 。その逆も同様です。タイプのプロパティは、タイプの同様のプロパティに対応していますが、多くの場合、それは単なる些細な割り当てではありません。NodeB*My*NodeA*My*NodeBNodeXMyNodeX

このような2つの類似したクラス構造がある場合:

// 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タイプの1つに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-> NodeX

// 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ラダーは本当に好きではありません。

MyNode-> Node ()の場合と同様に、Node-> MyNode( )の場合の各タイプにファクトリメソッドを設定することをお勧めします。しかし、明らかに少し問題があります。私はまた、私が今しなければならない2つのキャストをしたくないです。LoadCommitstaticvirtual

どういうわけかそのようなことを達成することは可能ですか?

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

あなたが上に提示したのと同じくらい一貫して名前が付けられていますか?

その場合、リフレクションを使用する一連の汎用の変換から変換への関数を作成できます。

これが私が考えていることです(これは意識の流れであり、検証されたコンパイル済みコードではありません):

<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 に答える