Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Способ POCO для реализации этого будет заключаться в том, чтобы спроектировать ваш интерфейс таким образом:

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

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

Ответ 2

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

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

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

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

Ответ 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, ...

}

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