代码之家  ›  专栏  ›  技术社区  ›  Byron Sommardahl

我应该使用可选的构造函数将域对象映射到视图模型吗?

  •  5
  • Byron Sommardahl  · 技术社区  · 14 年前

    我希望能够通过更新视图模型并将贡献的域模型作为参数(如下面的代码)传入,将域模型映射到视图模型。我的动机是避免重复使用映射代码,并提供一种简单的映射方法(还没有使用automapper)。一位朋友说视图模型不应该知道任何关于“付款”域模型的信息,该模型正被传递到可选的构造函数中。你怎么认为?

    public class LineItemsViewModel
    {
        public LineItemsViewModel()
        {
        }
    
        public LineItemsViewModel(IPayment payment)
        {
            LineItemColumnHeaders = payment.MerchantContext.Profile.UiPreferences.LineItemColumnHeaders;
            LineItems = LineItemDomainToViewModelMapper.MapToViewModel(payment.LineItems);
            ConvenienceFeeAmount = payment.ConvenienceFee.Fee;
            SubTotal = payment.PaymentAmount;
            Total = payment.PaymentAmount + payment.ConvenienceFee.Fee;
        }
    
        public IEnumerable<Dictionary<int, string>> LineItems { get; set; }
        public Dictionary<int, string> LineItemColumnHeaders { get; set; }
        public decimal SubTotal { get; set; }
        public decimal ConvenienceFeeAmount { get; set; }
        public decimal Total { get; set; }
    }
    
    2 回复  |  直到 9 年前
        1
  •  5
  •   John Farrell    14 年前

    你的朋友是对的。视图应该是哑的,对域模型一无所知。

    你试过用吗 Automapper 要将业务/域实体/模型映射到DTO/视图模型吗?

    更多详细信息,因为评论:

    在视图模型中放置映射代码违反了关注点分离、可靠的单一责任主体、MVC模式和域驱动的设计主体。视图有一个责任,把数据放到屏幕上,就是这样。我想没什么好争论的。这只是一个坏主意,违反了许多核心软件开发原则。

        2
  •  4
  •   Derek Greer    9 年前

    这是一个相当古老的问题,已经接受了一个答案,但为了详细说明所提供的答案并解决“评论”部分中出现的问题,正在提供此附加答案。

    正如所提出的,这个问题有点不确定。当被问到是否应该在软件开发方面采取某种方式时,这个问题可以理解为一个关于支配所讨论主题的基础设计原则的问题,如果不统一的话,这种设计原则应该被应用,或者两者都应用。为了有助于客观地讨论这个话题,让我们依次考虑这两个方面。

    创建一个构造函数以将值从一个对象映射到另一个对象的特殊实践在两个对象之间创建了耦合。视图模型包含与系统中特定视图相关的属性和/或行为。由于这样一个对象的目的是为一个特定的视图建模,因此封装映射逻辑以从系统中的另一个类型初始化模型的内部状态/值意味着视图模型现在包含了代码,这些代码可能需要修改,而不是更改视图的建模方式。这种变化可能会使模型行为的其他方面受到不利影响,从而导致系统行为的意外回归。控制系统内组件的去耦以通过耦合多个关注点防止行为意外回归的原则称为单一责任原则。

    何时应用这些原则的问题要困难得多。要记住,软件的编写通常要考虑一些目标(例如解决一些业务问题、促进娱乐或教育等),对于任何给定的软件来说,最适合的是与手头的任务相关的。为某一特定公司的旗舰产品而创建的软件系统所做的选择可能与为解决当前问题而开发的系统所做的选择大不相同。还需要考虑为促进某些类型的去耦所需的工作范围。有些去耦技术相对容易合并,而另一些则可能更难合并,甚至在初始实现以及每个新开发人员加入负责软件维护的团队时,都有步骤学习曲线。虽然没有一种启发式方法是做出此类决策的完美方法,但是测试驱动的开发提出了一种启发式方法,即在出现重复之前不引入抽象。例如,策略模式是一种很好的技术,可以坚持开放/关闭原则,这是一种控制对象设计的原则,允许在不同的场景中应用它们,而不需要修改现有的代码。然而,当遵循测试驱动的开发实践时,只有在观察到第二个用例之后,才能引入策略模式。通过这种启发式的方法,开发人员被迫将他们的工作限制在手头的任务上,只需编写完成任务所需的代码,而不需要重复,从而使浪费最小化和可维护性最大化(通过最小化复杂性)。

    然而,软件工程既是一门科学,也是一门艺术。这是一门科学,因为有规则控制着为达到某些目的而能做和不能做的事情,但这也是一门艺术,因为你做得越多越好,最终必须主观地做出明确的权衡。例如,作为一个客户机软件开发人员,我通常从不参与那些使用寿命短的应用程序的设计和开发。因此,在应用程序中引入基于约定的依赖注入之前,我不会等到看到重复。在应用程序中引入一致的依赖注入的使用,在软件系统的生命周期开始时的成本要比在您开始感觉到需要它之前的成本低得多。

    关于在视图模型中添加映射代码的具体示例,虽然它确实将视图模型与特定的域模型结合在一起,但在实践中,我不会发现这是一个很大的问题。视图模型不太可能与其他域模型一起使用,引入的代码类型(即映射)的性质通常不包含业务逻辑,因此导致系统中显著回归的SRP冲突的可能性远小于应用程序或域层的SRP冲突。

    也就是说,我不认为在构造函数中添加映射逻辑的过程可以节省任何重要的时间。如果要用大多数语言创建一个单独的类来封装域对象和视图模型之间的映射,我们只讨论了额外的几行代码。这是实现的区别:

    // constructor
    public ViewType(DomainType domainType) {
    ...
    }
    
    
    // mapper class
    public class ViewTypeMapper { 
      public ViewType Map(DomainType domainType) {
      ...
      }
    }
    

    所以,要么执行返回新的ViewType(DomainType),要么执行返回新的ViewTypeMapper().map(DomainType)。我只是不知道在这种情况下,去耦在哪里会增加任何重要的工作。在大多数情况下,您已经浪费了公司或客户的时间和金钱,甚至讨论了它,因为与创建单独的类来表示映射,或者继续创建automapper相比,最终讨论它的时间总是更长的。