12

私は、次のようなオブジェクトグラフを使用して、シンプルで小さなドメインモデルを作成しました。

-- Customer
    -- Name : Name
    -- Account : CustomerAccount
    -- HomeAddress : PostalAddress
    -- InvoiceAddress : PostalAddress
    -- HomePhoneNumber : TelephoneNumber
    -- WorkPhoneNumber : TelephoneNumber
    -- MobilePhoneNumber : TelephoneNumber
    -- EmailAddress : EmailAddress

この構造は、私が使用しなければならないレガシーデータベースと完全に対立しているため、顧客グラフの各要素のデータを含むフラットDTOを定義しました。データベースにビューとストアドプロシージャがあり、これにより、このフラットな構造を両方向に使用してデータを操作するには、これはすべて正常に機能し、ダンディです:)

挿入/更新のためにドメインモデルをDTOにフラット化するのは簡単ですが、私が問題を抱えているのは、DTOを取得し、そこからドメインモデルを作成することです...私の最初の考えは、顧客グラフを作成し、必要に応じてDTOから値を挿入します。これは、次のようなものです。

class CustomerVisitor
{
    public CustomerVisitor(CustomerDTO data) {...}

    private CustomerDTO Data;

    public void VisitCustomer(Customer customer)
    {
        customer.SomeValue = this.Data.SomeValue;
    }

    public void VisitName(Name name)
    {
        name.Title     = this.Data.NameTitle;
        name.FirstName = this.Data.NameFirstName;
        name.LastName  = this.Data.NameLastName;
    }

    // ... and so on for HomeAddress, EmailAddress etc...
}

それは理論であり、それが単純にそのようにレイアウトされているとき、それは健全な考えのように思えます:)

しかし、これが機能するためには、訪問者のermが訪問する前に、オブジェクトグラフ全体を構築する必要があります。そうしないと、NREが左右中央に表示されます。

私ができるようにしたいのは、訪問者が各要素を訪問するときにグラフにオブジェクトを割り当てられるようにすることです。目標は、DTOでデータが欠落しているオブジェクトの特殊なケースパターンを利用することです。

public void VisitMobilePhoneNumber(out TelephoneNumber mobileNumber)
{
    if (this.Data.MobileNumberValue != null)
    {
        mobileNumber = new TelephoneNumber
        {
            Value = this.Data.MobileNumberValue,
            // ...
        };
    }
    else
    {
        // Assign the missing number special case...
        mobileNumber = SpecialCases.MissingTelephoneNumber.Instance;
    }
}

私は正直にそれがうまくいくと思っていましたが、C#は私にエラーをスローします:

myVisitor.VisitHomePhone(out customer.HomePhoneNumber);

この方法でref/outパラメータを渡すことはできないので:(

したがって、独立した要素にアクセスし、完了したらグラフを再構築する必要があります。

Customer customer;
TelephoneNumber homePhone;
EmailAddress email;
// ...

myVisitor.VisitCustomer(out customer);
myVisitor.VisitHomePhone(out homePhone);
myVisitor.VisitEmail(out email);
// ...

customer.HomePhoneNumber = homePhone;
customer.EmailAddress = email;
// ...

この時点で、私はビジターパターンからかなり離れていて、ファクトリーにはるかに近いことを認識しており、最初からこのことに間違ってアプローチしたのではないかと考え始めています。

他の誰かがこのような問題に遭遇しましたか?どのようにしてそれを克服しましたか?このシナリオに適したデザインパターンはありますか?

そのようなお粗末な質問を投稿して申し訳ありません、そしてこれまで読んでくれてよくやった:)

編集FlorianGreinacherとgjvdkampからの有益な回答に応えて、私は次のような比較的単純なファクトリ実装に落ち着きました。

class CustomerFactory
{
    private CustomerDTO Data { get; set; }

    public CustomerFactory(CustomerDTO data) { ... }

    public Customer CreateCustomer()
    {
        var customer = new Customer();
        customer.BeginInit();
        customer.SomeFoo = this.Data.SomeFoo;
        customer.SomeBar = this.Data.SomeBar
        // other properties...

        customer.Name = this.CreateName();
        customer.Account = this.CreateAccount();
        // other components...

        customer.EndInit();
        return customer;
    }

    private Name CreateName()
    {
        var name = new Name();
        name.BeginInit();
        name.FirstName = this.Data.NameFirstName;
        name.LastName = this.Data.NameLastName;
        // ...
        name.EndInit();
        return name;
    }

    // Methods for all other components...
}

次に、データレイヤーとドメインモデル間の相互作用を処理するModelMediatorクラスを作成しました...

class ModelMediator
{
    public Customer SelectCustomer(Int32 key)
    {
        // Use a table gateway to get a customer DTO..
        // Use the CustomerFactory to construct the domain model...
    }

    public void SaveCustomer(Customer c)
    {
        // Use a customer visitor to scan for changes in the domain model...
        // Use a table gateway to persist the data...
    }
}
4

4 に答える 4

7

あなたはここで物事を本当に複雑にしすぎていると思います。ファクトリメソッドを使用して、ドメインオブジェクトが依存している他のドメインオブジェクトを明確に示すようにします。

class Customer
{
    private readonly Name name;
    private readonly PostalAddress homeAddress;

    public Customer(Name name, PostalAddress homeAddress, ...)
    {
        this.name = name;
        this.homeAddress = homeAddress;
        ...
    }
}

class CustomerFactory
{
    Customer Create(CustomerDTO customerDTO)
    {
        return new Customer(new Name(...), new PostalAdress(...));
    }
}

CustomerからCustomerDTOへの依存関係を取得する必要がある場合は、DTOをコンストラクターへの追加の引数として渡し、おそらく追加の抽象化でラップします。

このようにして、物事はクリーンでテスト可能で理解しやすい状態に保たれます。

于 2011-06-06T10:01:08.563 に答える
5

私は訪問者と一緒に行くとは思わない。これは、設計時に後で実行する必要のある操作がわからない場合に適しているため、クラスを開いて、他のユーザーがそのロジックを実装する訪問者を記述できるようにします。または、これでクラスを乱雑にしたくないほど多くのことを行う必要があります。

ここで実行したいのは、DTOからクラスのインスタンスを作成することです。クラスの構造とDTOは密接に関連しているため(DBでマッピングを行うので、その側ですべてのマッピングの問題を処理し、顧客の構造に直接マッピングするDTO形式を使用していると思います)。必要なものを設計します。多くの柔軟性は必要ありません。(ただし、コードが例外をスローすることなく、新しいフィールドなどのDTOへの変更を処理できるように堅牢にする必要があります)

基本的に、DTOのスニペットから顧客を構築する必要があります。XMLなど、どのような形式がありますか?

DTOを受け入れてCustomerを返すコンストラクターを探すだけだと思います(XMLの例:)

class Customer {
        public Customer(XmlNode sourceNode) {
            // logic goes here
        }
    }

Customerクラスは、DTOのインスタンスを「ラップアラウンド」して「1つになる」ことができます。これにより、DTOのインスタンスを顧客インスタンスに非常に自然に投影できます。

var c = new Customer(xCustomerNode)

これは、高レベルのパターン選択を処理します。これまでに同意しますか?これは、プロパティを「ref」で渡そうとする際に言及した特定の問題の問題です。DRYとKISSがどのように対立するかはわかりますが、考えすぎないようにします。かなり簡単な解決策でそれを修正できます。

したがって、PostalAddressの場合、Customer自体と同様に、独自のコンストラクターもあります。

public PostalAddress(XmlNode sourceNode){
   // here it reads the content into a PostalAddress
}

顧客について:

var adr = new PostalAddress(xAddressNode);

私がここで見ている問題は、これがInvoiceAddressまたはHomeAddressであるかどうかを判断するコードをどこに置くかということです。これは、PostalAddressのコンストラクターには属していません。後で、PostalAddressの他の用途がある可能性があるため、PostalAddressクラスにハードコーディングする必要はありません。

そのため、そのタスクはCustomerクラスで処理する必要があります。ここで、PostalAddressの使用法が決定されます。返されたアドレスから、それがどのタイプのアドレスであるかを判別できる必要があります。最も簡単なアプローチは、PostalAddressに次のようなプロパティを追加することだと思います。

public class PostalAddress{
  public string AdressUsage{get;set;} // this gets set in the constructor

}

DTOで指定するだけです。

<PostalAddress usage="HomeAddress" city="Amsterdam" street="Dam"/>

次に、Customerクラスでそれを確認し、適切なプロパティに「貼り付け」ます。

var adr = new PostalAddress(xAddressNode);
switch(adr.AddressUsage){
 case "HomeAddress": this.HomeAddress = adr; break;
 case "PostalAddress": this.PostalAddress = adr; break;
 default: throw new Exception("Unknown address usage");
}

顧客にそれがどのタイプのアドレスであるかを伝える単純な属性で十分だと思います。

これまでのところどのように聞こえますか?以下のコードはそれをすべてまとめています。

class Customer {

        public Customer(XmlNode sourceNode) {

            // loop over attributes to get the simple stuff out
            foreach (XmlAttribute att in sourceNode.Attributes) {
                // assign simpel stuff
            }

            // loop over child nodes and extract info
            foreach (XmlNode childNode in sourceNode.ChildNodes) {
                switch (childNode.Name) {
                    case "PostalAddress": // here we find an address, so handle that
                        var adr = new PostalAddress(childNode);
                        switch (adr.AddressUsage) { // now find out what address we just got and assign appropriately
                            case "HomeAddress": this.HomeAddress = adr; break;
                            case "InvoiceAddress": this.InvoiceAddress = adr; break;
                            default: throw new Exception("Unknown address usage");
                        }    
                        break;
                    // other stuff like phone numbers can be handeled the same way
                    default: break;
                }
            }
        }

        PostalAddress HomeAddress { get; private set; }
        PostalAddress InvoiceAddress { get; private set; }
        Name Name { get; private set; }
    }

    class PostalAddress {
        public PostalAddress(XmlNode sourceNode) {
            foreach (XmlAttribute att in sourceNode.Attributes) {
                switch (att.Name) {
                   case "AddressUsage": this.AddressUsage = att.Value; break;
                   // other properties go here...
            }
        }
    }
        public string AddressUsage { get; set; }

    }

    class Name {
        public string First { get; set; }
        public string Middle { get; set; }
        public string Last { get; set; }
    }

およびXMLのスニペット。DTO形式については何も言っていませんが、他の形式でも機能します。

<Customer>  
  <PostalAddress addressUsage="HomeAddress" city="Heresville" street="Janestreet" number="5"/>
  <PostalAddress addressUsage="InvoiceAddress" city="Theresville" street="Hankstreet" number="10"/>
</Customer>

よろしく、

Gert-Jan

于 2011-06-06T09:01:08.240 に答える
2

モデルクラスとDTOの間で変換を行う場合、私の好みは次の4つのいずれかを行うことです。

a。暗黙の変換演算子を使用します(特にjsonからdotnetへの遷移を処理する場合)。

public class Car
{
    public Color Color {get; set;}
    public int NumberOfDoors {get; set;}        
}

public class CarJson
{
    public string color {get; set;}
    public string numberOfDoors { get; set; }

    public static implicit operator Car(CarJson json)
    {
        return new Car
            {
                Color = (Color) Enum.Parse(typeof(Color), json.color),
                NumberOfDoors = Convert.ToInt32(json.numberOfDoors)
            };
    }
}

そして使用法は

    Car car = Json.Decode<CarJson>(inputString)

またはもっと簡単に

    var carJson = new CarJson {color = "red", numberOfDoors = "2"};
    Car car = carJson;

出来上がり、インスタント変換:)

http://msdn.microsoft.com/en-us/library/z5z9kes2.aspx

b。linqプロジェクションを使用してデータの形状を変更します

IQueryable<Car> cars = CarRepository.GetCars();
cars.Select( car => 
    new 
    { 
        numberOfDoors = car.NumberOfDoors.ToString(), 
        color = car.Color.ToString() 
    } );

c。2つの組み合わせを使用してください

d。拡張メソッドを定義します(これはlinqプロジェクションでも使用できます)

public static class ConversionExtensions
{
    public static CarJson ToCarJson(this Car car)
    {
        return new CarJson {...};
    }
}

CarRepository.GetCars().Select(car => car.ToCarJson());
于 2011-06-09T00:55:41.767 に答える
0

ここで説明したアプローチを取ることができます:フラットデータベースの結果セットをC#の階層オブジェクトコレクションに変換します

背後にある考え方は、Customerのようなオブジェクトを読み取り、それを辞書に入れることです。たとえばCustomerAccountのデータを読み取るときに、ディクショナリから顧客を取得して、顧客アカウントを顧客に追加できるようになりました。

オブジェクトグラフを作成するために、すべてのデータに対して1回だけ反復する必要があります。

于 2011-06-06T08:59:49.333 に答える