POCO,DTO,DLL和贫血领域模型

dan*_*dan 20 dll data-access-layer poco dto bll

我正在研究POCO和DTO之间差异(似乎POCO与行为有关(方法?))并且Martin Fowler在贫血领域模型上发现了这篇文章.

由于缺乏理解,我认为我已经创建了这些贫血领域模型之一.

在我的一个应用程序中,我将业务域实体定义为'dto'dll.他们有很多属性,包括getter和setter,而不是其他.我的业务逻辑代码(填充,计算)位于另一个"bll"dll中,我的数据访问代码位于"dal"dll中.我认为'最佳实践'.

所以通常我会像这样创建一个dto:

dto.BusinessObject bo = new dto.BusinessObject(...)
Run Code Online (Sandbox Code Playgroud)

并将其传递给bll层,如下所示:

bll.BusinessObject.Populate(bo);
Run Code Online (Sandbox Code Playgroud)

反过来,它执行一些逻辑并将其传递给dal层,如下所示:

dal.BusinessObject.Populate(bo);
Run Code Online (Sandbox Code Playgroud)

根据我的理解,为了使我的dto成为POCO,我需要将业务逻辑和行为(方法)作为对象的一部分.所以代替上面的代码更像是:

poco.BusinessObject bo = new poco.BusinessObject(...)
bo.Populate();
Run Code Online (Sandbox Code Playgroud)

即.我在对象上调用方法而不是将对象传递给方法.

我的问题是 - 我怎么能这样做,仍然保留关注点的'最佳实践'层次(单独的dll等...).不调用对象上的方法意味着必须在对象中定义方法?

请帮助我的困惑.

Mic*_*ows 22

通常,您不希望在域对象中引入持久性,因为它不是该业务模型的一部分(飞机不构建自身,它将乘客/货物从一个位置飞到另一个位置).您应该使用存储库模式,ORM框架或其他一些数据访问模式来管理对象状态的持久存储和撤销.

贫血领域模型的用武之地是当你做这样的事情时:

IAirplaneService service = ...;
Airplane plane = ...;
service.FlyAirplaneToAirport(plane, "IAD");
Run Code Online (Sandbox Code Playgroud)

在这种情况下,飞机状态的管理(无论是飞行状态,飞行地点,出发时间/机场是什么,到达时间/机场是什么,飞行计划是什么等)都被委托给飞机外部的东西. AirplaneService实例

实现此目的的POCO方法是以这种方式设计您的界面:

Airplane plane = ...;
plane.FlyToAirport("IAD");
Run Code Online (Sandbox Code Playgroud)

这是更容易被发现的,因为开发人员知道在哪里寻找飞机飞行(只需告诉飞机这样做).它还允许您确保在内部管理状态.然后,您可以将当前位置设置为只读,并确保它只在一个位置更改.对于贫血域对象,由于状态是在外部设置的,因此随着域的规模增加,发现状态变化的位置变得越来越困难.

  • 你知道这种设计的任何源代码示例吗?我发现这些原则很有意义,但是当我开始实现FlyToAirport时,如果我需要更新几个表,特别是如果我不使用SP,我最终会得到一个数据库的简洁接口. (4认同)

Hum*_*rto 10

我认为澄清这个的最好方法是定义:

DTO:数据传输对象:

它们仅用于表示层和服务层之间的数据传输.没有更多或更多.通常,它实现为带有gets和sets的类.

public class ClientDTO
{
    public long Id {get;set;}
    public string Name {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

BO:Business Objects:

业务对象代表业务元素,自然最佳实践表明它们也应包含业务逻辑.正如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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

服务或应用程序类 这些类表示用户和系统之间的交互,它们将使用ClientDTO和Client.

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Fre*_*örk 6

就个人而言,我没有发现那些贫血领域模型如此糟糕; 我真的很喜欢让域对象只表示数据,而不是行为.我认为这种方法的主要缺点是代码的可发现性; 您需要知道哪些操作可以使用它们.解决这个问题并且仍然将行为代码与模型分离的一种方法是为行为引入接口:

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, ...

}
Run Code Online (Sandbox Code Playgroud)

这样,您可以将行为实现与模型分开.注入模型的接口实现的使用也使代码更容易测试,因为您可以轻松地模拟行为.

  • 您如何管理行为与策略模式类似:(http://en.wikipedia.org/wiki/Strategy_pattern).当实际行为可能需要在运行时确定时,它确实很好,但是否则会导致过度设计.我喜欢这种模式,因为它使行为可以在类层次结构之外重用.但是,除非有必要避免使解决方案过于复杂,否则我必须有意识地避免使用它. (2认同)