22

POCO と DTO の違いを調べていたところ(POCO は動作 (メソッド?) を伴う dto のようです) 、貧血ドメイン モデルに関する Martin Fowler の記事に出会いました。

理解の欠如により、私はこれらの貧血ドメイン モデルの 1 つを作成したと思います。

私のアプリケーションの 1 つで、'dto' dll でビジネス ドメイン エンティティを定義しています。それらには、ゲッターとセッターを備えた多くのプロパティがあり、他にはあまりありません。ビジネス ロジック コード (入力、計算) は別の 'bll' dll にあり、データ アクセス コードは 'dal' dll にあります。「ベストプラクティス」と思いました。

したがって、通常、次のように dto を作成します。

dto.BusinessObject bo = new dto.BusinessObject(...)

次のように bll レイヤーに渡します。

bll.BusinessObject.Populate(bo);

次に、いくつかのロジックを実行し、次のように dal レイヤーに渡します。

dal.BusinessObject.Populate(bo);

私の理解では、dto を POCO にするには、ビジネス ロジックと動作 (メソッド) をオブジェクトの一部にする必要があります。したがって、上記のコードの代わりに、次のようになります。

poco.BusinessObject bo = new poco.BusinessObject(...)
bo.Populate();

すなわち。オブジェクトをメソッドに渡すのではなく、オブジェクトのメソッドを呼び出しています。

私の質問は、どうすればこれを行うことができ、「ベストプラクティス」の懸念事項の階層化 (個別の dll など...) を保持することができるかということです。オブジェクトでメソッドを呼び出すということは、オブジェクトでメソッドを定義する必要があるということではありませんか?

私の混乱を助けてください。

4

3 に答える 3

23

通常、ドメイン オブジェクトに永続性を導入することは望ましくありません。これは、ビジネス モデルの一部ではないためです (飛行機はそれ自体を構築せず、乗客/貨物をある場所から別の場所に飛ばします)。オブジェクトの状態の永続ストレージと取得を管理するには、リポジトリ パターンORM フレームワーク、またはその他のデータ アクセス パターンを使用する必要があります。

貧血ドメイン モデルが活躍するのは、次のようなことをしている場合です。

IAirplaneService service = ...;
Airplane plane = ...;
service.FlyAirplaneToAirport(plane, "IAD");

この場合、飛行機の状態の管理 (飛行しているかどうか、どこにいるのか、出発時刻/空港は何時か、到着時刻/空港は何時か、フライト プランは何かなど) は、飛行機の外部の何かに委任されます. . AirplaneService インスタンス。

これを実装する POCO の方法は、次のようにインターフェイスを設計することです。

Airplane plane = ...;
plane.FlyToAirport("IAD");

これは、開発者が飛行機を飛ばすためにどこを見ればよいかを知っているため、より発見しやすくなります (飛行機に飛ばすように指示するだけです)。また、状態が内部でのみ管理されるようにすることもできます。次に、現在の場所などを読み取り専用にして、1 か所だけが変更されるようにします。貧血ドメイン オブジェクトでは、状態が外部から設定されるため、ドメインの規模が大きくなるにつれて、状態が変更された場所を見つけることがますます難しくなります。

于 2009-05-26T17:44:14.350 に答える
10

これを明確にする最善の方法は、定義によるものだと思います。

DTO: データ転送オブジェクト:

それらは、通常、プレゼンテーション層とサービス層の間のデータ転送にのみ役立ちます。それ以下でもそれ以上でもありません。通常、gets と sets を持つクラスとして実装されます。

public class ClientDTO
{
    public long Id {get;set;}
    public string Name {get;set;}
}

BO: ビジネス オブジェクト:

ビジネス オブジェクトはビジネス要素を表し、当然のことながらベスト プラクティスではビジネス ロジックも含める必要があります。Michael Meadows が述べたように、このオブジェクトからデータ アクセスを分離することも良い方法です。

public class Client
{
    private long _id;
    public long Id 
    { 
        get { return _id; }
        protected set { _id = value; } 
    }
    protected Client() { }
    public Client(string name)
    {
        this.Name = name;    
    }
    private string _name;
    public string Name
    {
        get { return _name; }
        set 
        {   // Notice that there is business logic inside (name existence checking)
            // Persistence is isolated through the IClientDAO interface and a factory
            IClientDAO clientDAO = DAOFactory.Instance.Get<IClientDAO>();
            if (clientDAO.ExistsClientByName(value))
            {
                throw new ApplicationException("Another client with same name exists.");
            }
            _name = value;
        }
    }
    public void CheckIfCanBeRemoved()
    {
        // Check if there are sales associated to client
        if ( DAOFactory.Instance.GetDAO<ISaleDAO>().ExistsSalesFor(this) )
        {
            string msg = "Client can not be removed, there are sales associated to him/her.";
            throw new ApplicationException(msg);
        }
    }
}

サービスまたはアプリケーション クラス これらのクラスは、ユーザーとシステム間の対話を表し、ClientDTO とクライアントの両方を利用します。

public class ClientRegistration
{
    public void Insert(ClientDTO dto)
    {
        Client client = new Client(dto.Id,dto.Name); /// <-- Business logic inside the constructor
        DAOFactory.Instance.Save(client);        
    }
    public void Modify(ClientDTO dto)
    {
        Client client = DAOFactory.Instance.Get<Client>(dto.Id);
        client.Name = dto.Name;  // <--- Business logic inside the Name property
        DAOFactory.Instance.Save(client);
    }
    public void Remove(ClientDTO dto)
    {
        Client client = DAOFactory.Instance.Get<Client>(dto.Id);
        client.CheckIfCanBeRemoved() // <--- Business logic here
        DAOFactory.Instance.Remove(client);
    }
    public ClientDTO Retrieve(string name)
    {
        Client client = DAOFactory.Instance.Get<IClientDAO>().FindByName(name);
        if (client == null) { throw new ApplicationException("Client not found."); }
        ClientDTO dto = new ClientDTO()
        {
            Id = client.Id,
            Name = client.Name
        }
    }
}
于 2009-12-02T11:24:50.030 に答える
6

個人的には、これらの貧血ドメイン モデルがそれほど悪いとは思いません。動作ではなく、データのみを表すドメイン オブジェクトを持つというアイデアが本当に気に入っています。このアプローチの主な欠点は、コードの発見可能性だと思います。それらを使用できるアクションを知る必要があります。これを回避し、ビヘイビア コードをモデルから分離したままにする 1 つの方法は、ビヘイビアのインターフェイスを導入することです。

interface ISomeDomainObjectBehaviour
{
    SomeDomainObject Get(int Id);
    void Save(SomeDomainObject data);
    void Delete(int Id);
}

class SomeDomainObjectSqlBehaviour : ISomeDomainObjectBehaviour
{
    SomeDomainObject ISomeDomainObjectBehaviour.Get(int Id)
    {
        // code to get object from database
    }

    void ISomeDomainObjectBehaviour.Save(SomeDomainObject data)
    {
        // code to store object in database
    }

    void ISomeDomainObjectBehaviour.Delete(int Id)
    {
        // code to remove object from database
    }
}
class SomeDomainObject
{
    private ISomeDomainObjectBehaviour _behaviour = null;
    public SomeDomainObject(ISomeDomainObjectBehaviour behaviour)
    {

    }

    public int Id { get; set; }
    public string Name { get; set; }
    public int Size { get; set; }


    public void Save()
    {
        if (_behaviour != null)
        {
            _behaviour.Save(this);
        }
    }

    // add methods for getting, deleting, ...

}

そうすれば、動作の実装をモデルから分離したままにすることができます。モデルに注入されたインターフェイス実装を使用すると、動作を簡単にモックできるため、コードのテストもかなり簡単になります。

于 2009-05-22T05:54:39.070 に答える