POCO, DTO, DLL и модели анемичных доменов - PullRequest
20 голосов
/ 22 мая 2009

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

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

В одном из моих приложений у меня есть сущности бизнес-домена, определенные в dll 'dto'. У них много свойств, как у геттера, так и у сеттера, и не намного. Мой код бизнес-логики (заполнить, вычислить) находится в другой dll «bll», а мой код доступа к данным - в dll «dal». «Лучшая практика», - подумал я.

Поэтому обычно я создаю 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 и т. Д.). Разве вызов метода для объекта не означает, что метод должен быть определен в объекте?

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

Ответы [ 3 ]

22 голосов
/ 26 мая 2009

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

следует использовать шаблон репозитория , ORM или другой шаблон доступа к данным.

Когда модель анемичного домена начинает играть, когда вы делаете такие вещи:

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

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

Способ реализации POCO заключается в разработке интерфейса следующим образом:

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

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

10 голосов
/ 02 декабря 2009

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

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

Они служат только для передачи данных, как правило, между уровнем представления и уровнем обслуживания. Ничего меньше или больше. Обычно это реализовано как класс с get и 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, так и Клиент.

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
        }
    }
}
6 голосов
/ 22 мая 2009

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

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

}

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

...