【问题标题】:POCO's, DTO's, DLL's and Anaemic Domain ModelsPOCO、DTO、DLL 和贫血域模型
【发布时间】:2010-10-28 03:27:00
【问题描述】:

我正在查看 differences between POCO and DTO(看来 POCO 是 dto 的行为(方法?))并遇到了 Martin Fowler 在贫血域模型上的 this article

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

在我的一个应用程序中,我在“dto”dll 中定义了我的业务域实体。它们有很多与 getter 和 setter 相关的属性,其他的不多。我的业务逻辑代码(填充、计算)在另一个“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 等......)。在对象上调用方法不是说必须在对象中定义方法吗?

请帮我解惑。

【问题讨论】:

    标签: dll poco data-access-layer dto bll


    【解决方案1】:

    通常,您不想在域对象中引入持久性,因为它不是该业务模型的一部分(飞机不会自行构建,它会将乘客/货物从一个位置运送到另一个位置)。您应该使用repository patternORM framework 或其他一些数据访问模式来管理对象的状态的持久存储和检索。

    当你做这样的事情时,贫血领域模型就会发挥作用:

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

    在这种情况下,飞机状态的管理(是否正在飞行、在哪里、起飞时间/机场是什么、到达时间/机场是什么、飞行计划是什么等)被委托给外部的东西飞机... AirplaneService 实例。

    实现这一点的 POCO 方式是这样设计您的界面:

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

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

    【讨论】:

    • 您知道这种设计的任何源代码示例吗?我发现这些原则是有道理的,但是当我开始实施 FlyToAirport 时,如果我需要更新几个表,尤其是在我不使用 SP 的情况下,我最终会得到一个与数据库的聊天界面。
    • 当涉及到外部事务时,使用服务更有意义。您应该尝试使这些尽可能粗粒度,以便您可以在域模型中嵌入尽可能多的行为。不幸的是,我没有看到它被尽可能频繁地使用。当然,我在我编写的任何非遗留生产代码中都使用它,并且已经参与了几个使用这种方法的项目。
    【解决方案2】:

    我认为澄清这一点的最佳方法是通过定义:

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

    服务或应用程序类 这些类代表 User 和 System 之间的交互,它们将同时使用 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
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 管理行为的方式类似于策略模式:(en.wikipedia.org/wiki/Strategy_pattern)。当实际行为可能需要在运行时确定时非常好,但在其他情况下可能会导致过度设计。我喜欢这种模式,因为它使行为可以在类层次结构之外重用。但是,除非必要,否则我必须有意识地避免使用它,以免解决方案过于复杂。
      • 是的,这就像将行为委托给一个单独的类,但调用代码仍在通过您的域对象访问该行为。 (也许您可以将行为类设为内部以确保这一点)
      猜你喜欢
      • 2011-09-19
      • 2017-10-29
      • 1970-01-01
      • 2010-12-26
      • 2018-12-14
      • 2012-02-04
      • 2010-12-20
      • 2010-11-04
      • 2014-06-12
      相关资源
      最近更新 更多