Модели POCO, DTO, DLL и анемичных доменов

Я смотрел на различия между POCO и DTO (похоже, что POCO являются dto с поведением (методы?))и наткнулся в этой статье Мартин Фаулер о модели анемического домена.

из-за отсутствия понимания, я думаю, что я создал одну из этих анемичных моделей домена.

в одном из моих приложений у меня есть объекты бизнес-домена, определенные в dll "dto". У них есть много свойств вместе с геттер и сеттера и ничего больше. Мой код бизнес-логики (заполнять, вычислять) находится в другой " 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();

ie. Я вызываю метод на объекте, а не передаю объект методу.

мой вопрос - как я могу это сделать и по-прежнему сохранять "наилучшую практику" расслоения проблем (отдельные dll и т. д...). Не означает ли вызов метода на объекте, что метод должен быть определен в объекте?

пожалуйста, помогите моему замешательству.

3 ответов


Как правило, вы не хотите вводить постоянство в объекты домена, поскольку оно не является частью этой бизнес-модели (самолет не строит себя, он перевозит пассажиров/груз из одного места в другое). Вы должны использовать шаблон репозитория, an ORM framework, или какой-либо другой шаблон доступа к данным для управления постоянным хранилищем и retreival объекта государство.

где анемичной модели предметной области в играть, когда вы делаете вещи, как это:

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

в этом случае управление состоянием самолета (будь то полет, где он находится, время вылета/аэропорт, время прибытия/аэропорт, план полета и т. д.) делегируется чему-то внешнему по отношению к самолету... экземпляр AirplaneService.

способом реализации POCO было бы создать ваш интерфейс таким образом:

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

это более заметным, поскольку разработчики знают, где искать, чтобы заставить самолет летать (просто скажите самолету сделать это). Это также позволяет вам гарантировать, что состояние только управлял внутренне. Затем вы можете сделать такие вещи, как текущее местоположение только для чтения, и убедиться, что оно изменено только в одном месте. С анемичным объектом домена, так как состояние задается извне, обнаружение, где состояние изменяется, становится все труднее по мере увеличения масштаба домена.


Я думаю, что лучший способ прояснить это определение:

DTO: объекты передачи данных:

Они служат только для транспортировки данных, как правило, между уровнем представления и уровнем службы. Ни больше, ни меньше. Как правило, он реализуется как класс с gets и sets.

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

BO: бизнес-объекты:

бизнес-объекты представляют бизнес-элементы и, естественно, лучшая практика говорит, что они должна содержать и бизнес-логику. По словам Майкла Медоуза, также рекомендуется изолировать доступ к данным от этих объектов.

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, так и 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
        }
    }
}

лично я не нахожу эти анемичные модели доменов настолько плохими; мне очень нравится идея иметь объекты домена, которые представляют только данные, а не поведение. Я думаю, что основным недостатком этого подхода является понятность кода; Вы должны знать, какие действия доступны для их использования. Один из способов обойти это и все же сохранить код поведения, отделенный от модели, - это ввести интерфейсы для поведения:

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

}

таким образом, вы можете сохранить поведение реализация отделена от модели. Использование реализаций интерфейса, которые вводятся в модель, также упрощает тестирование кода, поскольку вы можете легко издеваться над поведением.