在视图模型中使用业务对象


11

使用可重用的业务对象时,在构建视图模型时什么是最佳实践?

我们使用调用的对象Builder来构建视图模型。每个视图逻辑单元(订单,用户等)的一个构建器,其中每个单元可以包含许多不同的视图模型(订单包含摘要,订单行等)。

建造者可以通过一个或多个标准业务对象提取数据,以建立视图模型。

在视图模型中使用业务对象/模型时,什么被认为是更好的做法?

方法1

允许在视图模型中使用业务对象?

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

方法2

仅从业务对象中获取必要的数据

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

我可以看到两者的优点和缺点,但是我想知道是否存在一种公认的方法?在方法1中,模型之间没有代码重复,但是会创建对业务逻辑的依赖。在方法2中,您仅获取视图所需的数据,但是在模型周围重复了代码。

Answers:


12

选项1在域模型和视图之间创建紧密耦合。这与视图模型设计要解决的非常矛盾。

如果视图本身发生更改,则视图将建模为“更改原因”。通过将域模型对象放在视图模型中,您将介绍另一个更改原因(例如,域已更改)。这清楚地表明违反了单一责任原则。有两个或两个以上的更改原因导致视图模型需要大量维护-可能比跨域/视图模型重复进行的可感知维护成本要高。

我将始终主张方法2。通常情况下,视图模型看起来非常相似,甚至与领域模型对象相同,但是正如我提到的,区别在于它们改变的原因不同。


我是否正确地认为,“更改原因”是指维护意义上的变化,而不是更新(例如ui事件)意义上的变化?
安迪·亨特

@AndyBursh是的-参见本文,特别是“ Robert C. Martin将责任定义为变更的原因,并得出结论,一个类或模块应该只有一个变更理由”。
MattDavey 2012年

我喜欢您的回答,但有一些想法...视图模型不一定只是因为模型改变而改变。仅当您绑定或使用已更改的特定属性时,这才是问题,因为您的引用是针对整个对象的。引用域对象可以使更改和重新保存变得更加容易。您的保存方法也依赖于域对象,因此您必须将视图模型转换回去,或者将您的业务方法设置为接受不好的视图模型。我仍然认为#2最有意义,但仅差2美分。
KingOfHypocrites

如果虚拟机中不能包含域对象,那么您将如何表示更复杂的东西,例如订单数组?
杰夫

因此,这是否意味着诸如格式化用于用户显示的时间戳之类的事情应该属于视图层,而不属于域层,并且域级别的对象应该仅向视图对象返回未格式化的原始时间戳,而后者应该是包含格式化程序逻辑?
The_Sympathizer

2

选项1是可取的,因为它可以避免代码重复。而已。

如果域模型发生重大变化,则几乎可以肯定,视图还是必须进行更改。使用选项2,则必须更改视图模型,构建器以及视图本身。这种事情绝对是可维护性的毒药。亚尼

具有单独的视图模型的目的是将仅对视图有意义的状态(例如,当前选择的选项卡)与业务模型保持独立。但是业务数据本身应该被重用而不是重复。


YAGNI-解决大多数软件设计问题的秘密刺客。
马丁·布洛

6
抱歉,这对于除最琐碎的应用程序之外的所有应用程序都是可怕的建议。视图模型没有状态。它们是数据传输对象。选择哪个选项卡是视图结构的一部分,并且与视图模型中的数据无关。如果您正确地构建程序并使用诸如Automapper之类的工具充实您的视图模型,那么维护并不是一场噩梦。
路西法·山姆

“如果域模型发生重大变化,则几乎可以肯定,视图也将必须进行更改。” -同意 但是,如果您对域名进行了少量更改,该怎么办?使用选项一,对域的每个微小更改(甚至只是重命名属性)都需要对视图进行相应更改。这也是维护性的绝对毒药。
MattDavey 2012年

@MattDavey:如果重命名属性,则必须使用单独的视图模型来更改视图(或域和视图模型之间的任何映射),并且同一名称现在具有两个不同的名称,这肯定会引起混淆。
Michael Borgwardt 2012年

@Lucifer Sam:显然,我们对视图模型的概念非常不同。您的声音听起来让我非常非常奇怪,就像您要描述用于哑终端的大型机应用程序一样,但肯定不是现代Web或胖客户端应用程序。
Michael Borgwardt 2012年

2

原则和口头禅有时对指导设计很有价值……但这是我的实际答案:

假设您的视图模型被序列化为JSON或XML。如果您尝试对域模型进行序列化,最终将导致混乱的文本,并且很可能会遇到循环引用等问题。

视图模型的目的不是将域模型分组在一起,以便视图可以使用它们。取而代之的是,视图模型应该是视图的完全平面模型……您正在屏幕上查看的实际内容。您的视图逻辑仅应与结构化视图模型中存在的数据有关。

理想情况下,您的视图模型应该几乎完全由预先格式化的字符串组成。考虑一下...您甚至不需要在视图模型中使用DateTime或小数,因为那样您就无法使用C#,Javascript,Objective-C等格式逻辑。


2
我从来没有与序列化域模型的任何问题。并将所有内容转换为模型中的字符串?认真吗
Michael Borgwardt

3
@MichaelBorgwardt是的,这就是视图模型。您不想序列化域模型并将其发送到整个地方。所有业务逻辑都应安全地放在家里。但是,视图应该是灵活的,并且可以在任何设备上呈现,这就是为什么要完全分离STRUCTURE,DATA和STYLE的原因。
路西法·山姆

抱歉,对于任何应用程序,这都是可怕的建议。这导致过度设计的应用程序充满了重复的代码,而这些代码恰好与flexible相反。
Michael Borgwardt

1
@MichaelBorgwardt听起来您已经习惯于使用贫血的领域模型,在这些模型中,实体只不过是行为很少或根本没有行为的财产包。在那种情况下,DTO /视图模型基本上是重复的。但是,如果您有一个具有复杂关系的丰富域模型,则必须有一层DTO /视图模型,并且它们将与域实体不太相似。
MattDavey 2012年

@MattDavey:听起来您习惯使用的域模型不仅是丰富的,而且是名副其实的盗窃者。我也不喜欢贫血模型,但是它们仍然是模型,它们的行为应仅限于表示领域。单一责任原则以及所有这些……
Michael Borgwardt 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.