Вопрос

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

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

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

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

Это было полезно?

Решение

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

Анемичная доменная модель вступает в игру, когда вы делаете такие вещи:

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top