Usando objetos de negócios em modelos de exibição

11

Ao usar objetos de negócios reutilizáveis, o que é considerado uma prática recomendada ao criar modelos de vista?

Usamos um objeto que chamamos Builderpara construir nossos modelos de exibição. Um construtor para cada unidade lógica de visualizações (pedidos, usuários etc.), em que cada unidade pode conter um número de modelos de visualização diferentes (pedidos contém resumo, linhas de pedidos etc.).

Um construtor pode extrair dados através de um ou mais objetos de negócios padrão para construir um modelo de visualização.

Qual é considerada a melhor prática quando se trata de usar objetos / modelos de negócios em modelos de exibição?

Abordagem 1

Permitir o uso de objetos de negócios no modelo de exibição?

//Business object in some library
public class Order
{
    public int OrderNum;
    public int NumOrderLines;
    //...
}

//Order builder in website
public class OrderBuilder
{
    public OrderSummary BuildSummaryForOrder(int OrderNum)
    {
        Some.Business.Logic.Order obOrder = Some.Business.Logic.GetOrder(OrderNum);
        //Any exception handling, additional logic, or whatever

        OrderSummary obModel = new OrderSummary();
        obModel.Order = obOrder;

        return obModel;
    }
}

//View model
public class OrderSummary
{
    public Some.Business.Logic.Order Order;
    //Other methods for additional logic based on the order
    //and other properties
}

Abordagem 2

Pegue apenas os dados necessários dos objetos de negócios

//Business object in some library
public class Order
{
    public int OrderNum;
    public int NumOrderLines;
    //...
}

//Order builder in website
public class OrderBuilder
{
    public OrderSummary BuildSummaryForOrder(int OrderNum)
    {
        Some.Business.Logic.Order obOrder = Some.Business.Logic.GetOrder(OrderNum);
        //Any exception handling, additional logic, or whatever

        OrderSummary obModel = new OrderSummary()
        {
            OrderNum = obOrder.OrderNum,
            NumOrderLnes = obOrder.NumOrderLines,
        }

        return obModel;
    }
}

//View model
public class OrderSummary
{
    public int OrderNum;
    public int NumOrderLines
    //Other methods for additional logic based on the order
    //and other properties
}

Posso ver os benefícios e as desvantagens de ambos, mas me pergunto se existe uma abordagem aceita? Na abordagem 1, não há duplicação de código nos modelos, mas cria uma dependência da lógica de negócios. Na abordagem 2, você pega apenas os dados necessários para a exibição, mas duplica o código em torno dos modelos.

Andy Hunt
fonte

Respostas:

12

A opção 1 cria um forte acoplamento entre o modelo de domínio e a visualização. Isso contraria os próprios modelos de exibição de problemas criados para solucionar.

Um modelo de visualização "motivo para mudar" é se a própria visualização é alterada. Ao colocar um objeto de modelo de domínio no modelo de exibição, você está apresentando outro motivo para mudar (por exemplo, o domínio mudou). Esta é uma indicação clara de uma violação do princípio da responsabilidade única. Ter dois ou mais motivos para mudar leva a exibir modelos que exigem muita manutenção - provavelmente mais do que o custo de manutenção percebido da duplicação nos modelos de domínio / exibição.

Eu sempre defendia a abordagem 2. Geralmente, os modelos de exibição podem parecer muito semelhantes, até idênticos aos objetos de modelo de domínio, mas a distinção, como eu mencionei, são os diferentes motivos de mudança.

MattDavey
fonte
Estou correto ao pensar que, por "razão para mudar", você quer dizer mudança no sentido de manutenção, não mudança no sentido de atualização (por exemplo, evento da interface do usuário)?
Andy Hunt
@AndyBursh sim, está correto - veja este artigo , particularmente a linha "Robert C. Martin define uma responsabilidade como uma razão para mudar e conclui que uma classe ou módulo deve ter uma e apenas uma razão para mudar".
precisa saber é o seguinte
Eu gosto da sua resposta, mas alguns pensamentos ... O modelo de visualização não muda necessariamente apenas porque o modelo muda. Somente se você estivesse vinculando ou usando uma propriedade específica alterada, isso seria um problema, pois sua referência é ao objeto inteiro. Ter uma referência ao objeto de domínio facilita fazer alterações e salvá-lo novamente. Seus métodos de salvamento também dependem do objeto de domínio; portanto, você deve converter o modelo de visualização novamente ou configurar seu método de negócios para aceitar modelos de visualização que também não são bons. Eu ainda acho que o número 2 faz mais sentido, mas apenas por dois centavos.
KingOfHypocrites
Se você não pode ter objetos de domínio em uma VM, como você representaria algo mais complicado, como uma matriz de pedidos?
Jeff
Isso significa que coisas como, digamos, formatar um carimbo de data e hora para exibição do usuário devem pertencer à camada de visualização, não à camada de domínio, e os objetos no nível de domínio devem retornar apenas um carimbo de data / hora não formatado e bruto aos objetos de exibição e os últimos devem contém a lógica do formatador?
The_Sympathizer
2

A opção 1 é preferível, pois evita a duplicação de código. É isso aí.

Se o modelo de domínio mudar significativamente, é quase certo que a visualização precisará mudar de qualquer maneira. Com a opção 2, é necessário alterar o modelo de vista E o construtor, bem como a própria vista. Esse tipo de coisa é um veneno absoluto para a manutenção. YAGNI.

O objetivo de ter um modelo de vista separado é manter o estado que é significativo apenas para a vista (por exemplo, qual guia está selecionada no momento) separada do modelo de negócios. Mas os dados de negócios em si devem ser reutilizados em vez de duplicados.

Michael Borgwardt
fonte
YAGNI - o assassino secreto de resolver a maioria dos problemas de design de software.
Martin Blore
6
Sinto muito, mas este é um conselho horrível para todos, exceto para os aplicativos mais triviais. Os modelos de exibição não têm estado. Eles são objetos de transferência de dados. O separador que está seleccionado faz parte da ESTRUTURA da vista e não tem NADA a ver com os DADOS no modelo de vista. A manutenção não é um pesadelo se você estruturar seu programa corretamente e usar algo como o Automapper para hidratar seus modelos de exibição.
Lucifer Sam
"Se o modelo de domínio mudar significativamente, é quase certo que a visualização precisará mudar de qualquer maneira". - Acordado. Mas e quando você tem uma pequena alteração no domínio? Com a opção um, toda pequena alteração no domínio (mesmo que apenas renomeie uma propriedade) exige uma alteração correspondente na exibição. Isso também é um veneno absoluto para a manutenção.
MattDavey
@MattDavey: se você renomear uma propriedade, com um modelo de exibição separado, também será necessário alterar a exibição (ou qualquer mapa entre domínio e modelo de exibição) e agora terá dois nomes diferentes para a mesma coisa, o que certamente causará confusão.
Michael Borgwardt
@ Lucifer Sam: obviamente, temos conceitos muito diferentes do que é um modelo de exibição. O seu parece muito, muito estranho para mim, como se você estivesse descrevendo aplicativos de mainframe para terminais idiotas, mas certamente não aplicativos da Web modernos ou de clientes gordos.
Michael Borgwardt
2

Por vezes, princípios e mantras são valiosos para orientar o design ... mas aqui está a minha resposta prática:

Imagine seus modelos de exibição sendo serializados em JSON ou XML. Se você tentar serializar seus modelos de domínio, terá uma bagunça hedionda de texto e provavelmente encontrará problemas com referências circulares e outros.

O objetivo de um modelo de visualização não é agrupar modelos de domínio para que a visualização possa consumi-los. Em vez disso, o modelo de vista deve ser um modelo completamente plano da vista ... a coisa real que você está vendo na tela. Sua lógica de visualização deve se preocupar apenas com a estruturação dos dados presentes no modelo de visualização.

Idealmente, seu modelo de visualização deve ser composto quase inteiramente de sequências de caracteres pré-formatadas. Pense nisso ... você nem quer um DateTime ou um decimal no seu modelo de exibição, porque fica parado fazendo lógica de formatação em C #, Javascript, Objective-C, etc.

Lúcifer Sam
fonte
2
Nunca tive problemas com a serialização de modelos de domínio. E convertendo tudo em strings em um modelo? Seriamente?
Michael Borgwardt
3
@MichaelBorgwardt Sim, isso é o que um modelo de exibição deve ser. Você não deseja serializar seus modelos de domínio e enviá-los para todo o lugar. Toda lógica de negócios deve permanecer em segurança em casa em um só lugar. As visualizações, no entanto, devem ser flexíveis e poderem ser renderizadas em qualquer dispositivo, e é por isso que você deseja separar completamente sua ESTRUTURA, DADOS e ESTILO.
Lucifer Sam
Desculpe, mas esse é um conselho horrível para qualquer aplicativo, período. Isso leva a aplicativos com excesso de engenharia, cheios de código duplicado, exatamente o oposto de flexível.
Michael Borgwardt
1
@MichaelBorgwardt parece que você está acostumado a trabalhar com modelos de domínio anêmicos, em que as entidades são pouco mais que bolsas de propriedades com pouco ou nenhum comportamento. Nesse caso, sim, um modelo DTO / View seria basicamente uma duplicata. No entanto, se você tiver um modelo de domínio rico com relacionamentos complexos, será necessária uma camada de DTOs / modelos de exibição, e eles não serão tão semelhantes às entidades do domínio.
precisa saber é o seguinte
@MattDavey: Parece que os modelos de domínio com os quais você está acostumado a trabalhar não são apenas ricos, mas também verdadeiros cleptocratas. Também não gosto de modelos anêmicos, mas eles ainda são modelos, e seu comportamento deve limitar-se a representar o domínio. Princípio de responsabilidade única e tudo o que ...
Michael Borgwardt