我应该使用可选构造函数将域对象映射到视图模型吗?
我希望能够通过更新视图模型并将贡献域模型作为参数传递(如下面的代码)来将域模型映射到视图模型。我的动机是避免重复使用映射代码并提供一种简单的映射方法(尚未使用自动映射器)。一位朋友说视图模型不应该知道有关传递给可选构造函数的“付款”域模型的任何信息。你怎么认为?
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; }
}
I'd like to be able to map a domain model to a view model by newing up a view model and passing in the contributing domain model as a parameter (like the code below). My motivation is to keep from re-using mapping code AND to provide a simple way to map (not using automapper yet). A friend says the view model should not know anything about the "payment" domain model that's being passed into the optional constructor. What do you think?
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; }
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是一个相当古老的问题,其答案已被接受,但为了详细说明所提供的答案并解决评论部分中出现的问题,我们提供了此附加答案。
正如所提出的,这个问题有点不确定。当被问及在软件开发方面是否应该以某种方式完成某些事情时,该问题可以被理解为关于支配所讨论主题的底层设计原则的问题,当这些设计原则不统一时应该应用,或者两者兼而有之。为了帮助客观地讨论该主题,让我们依次考虑这两个方面。
创建构造函数以将值从一个对象映射到另一个对象的特定做法会在两个对象之间创建耦合。视图模型包含与系统内特定视图有关的属性和/或行为。由于此类对象的目的是对特定视图进行建模,因此封装用于从系统内的另一种类型初始化模型的内部状态/值的映射逻辑意味着视图模型现在包含可能因某种原因需要修改的代码除了视图建模方式的更改之外。这种变化可能会导致模型行为的其他方面受到不利影响,从而导致系统行为意外回归。管理系统内组件解耦以防止通过耦合多个关注点而导致行为意外回归的原则称为单一职责原则。
何时应用这些原则的问题有点困难。重要的是要记住,软件通常是在编写时考虑到一些目标(例如解决一些业务问题、促进娱乐或教育等),并且对于任何给定的软件来说最好的都是与手头的任务相关的。为作为特定公司的旗舰产品而创建的软件系统所做的选择可能与为解决眼前问题而开发的系统所做的选择有很大不同。还需要考虑促进某些类型的解耦所需的工作范围。一些解耦技术相对容易整合,而另一些解耦技术可能更困难,甚至为初始实现以及添加到负责软件维护的团队中的每个新开发人员提供了逐步学习曲线。虽然没有任何启发式方法能够完美地做出此类决策,但测试驱动开发提出了在出现重复之前不引入抽象的启发式方法。例如,策略模式是一种遵守开放/封闭原则的优秀技术,该原则控制对象的设计,以允许它们在不同的场景中应用,而无需修改现有代码。然而,当遵循测试驱动开发实践时,在观察到第二个用例之前不会引入策略模式。通过遵循这种启发式,开发人员被迫将他们的精力限制在手头的任务上,只编写完成任务所需的代码而不重复,从而最大限度地减少浪费并最大化可维护性(通过最小化复杂性)。
尽管如此,软件工程既是一门科学,也是一门艺术。它是一门科学,因为有一些规则来控制为了实现某些目标可以做什么和不可以做什么,但它也是一门艺术,因为你做得越多,就会做得越好,并且需要做出明确的权衡最终必须主观地做出。例如,作为一名客户端软件开发人员,我通常从不参与生命周期较短的应用程序的设计和开发。因此,我不会等到看到重复才将基于约定的依赖项注入引入到我的应用程序中。在软件系统生命周期之初,在应用程序中引入一致使用依赖注入的成本比等到您开始感觉到需要它时要低得多。
关于在视图模型中添加映射代码的具体示例,虽然它确实将视图模型耦合到特定的域模型,但实际上我不认为这是一个大问题。视图模型不太可能与其他域模型一起使用,并且引入的代码类型(即映射)的性质通常不包含业务逻辑,因此这种 SRP 违规导致系统显着回归的可能性是远小于应用程序或域层的 SRP 违规。
也就是说,我不认为在构造函数中添加映射逻辑的过程可以节省任何时间。如果要创建一个单独的类来封装大多数语言中领域对象和视图模型之间的映射,我们只讨论额外的几行代码。这是实现上的区别:
因此,您要么执行 return new ViewType(domainType),要么执行 return new ViewTypeMapper().Map(domainType)。我只是看不出在这种情况下解耦会增加任何重要的工作。在大多数情况下,即使进行讨论,您也已经浪费了公司或客户的时间和金钱,因为与仅创建单独的类来表示相比,您最终总是会花费更长的时间来讨论它映射,或者如果您要继续设置 Automapper。
This is a rather old question for which an answer has already been accepted, but to elaborate upon the provided answer and to address questions that have come up in the comments section, this additional answer is being provided.
As posed, the question is a bit indefinite. When asked whether something should be done a certain way with respect to software development, the question could be understood to be a question concerning the underlying design principles that govern the topic in question, when such design principles should be applied if not uniformly, or both. To aid in discussing the topic objectively, let's consider these two aspects in turn.
The particular practice of creating a constructor to map the values from one object to another creates a coupling between the two objects. A view model contains the properties and/or the behavior pertaining to a particular view within the system. Since the purpose of such an object is to model a particular view, encapsulating the mapping logic for initializing the internal state/values of the model from another type within the system means that the view model now contains code which may need to be modified for reasons other than changes to how the view is modeled. Such changes open up the possibility that other aspects of the model's behavior may be adversely affected, thus causing unintended regression in the behavior of the system. The principle governing the decoupling of components within the system to prevent unintended regression of behavior by coupling multiple concerns is called The Single Responsibility Principle.
The question of when such principles should be applied is a bit more difficult. It's important to keep in mind that software is typically written with some goal in mind (e.g. solving some business problems, facilitating entertainment or education, etc.) and what is best for any given piece of software is relative to the task at hand. The choices made for a software system that's being created to serve as the flagship product for a particular company may be quite different than the choices made for a system being developed to solve an immediate problem. The scope of work required in order to facilitate certain types of decoupling also need to be considered. Some decoupling techniques are relatively easy to incorporate while others may be more difficult and even come with step learning curves for the initial implementation as well as for each new developer added to the team responsible for the software's maintenance. While no heuristic is perfect for making such decisions, Test-Driven Development sets forth the heuristic of not introducing abstractions until duplication is present. For example, the strategy pattern is an excellent technique for adhering to the Open/Closed Principle, a principle governing the design of objects to allow for their application in different scenarios without needing to modify the existing code. When following Test-Driven Development practices, however, one wouldn't introduce the strategy pattern until a second use-case was observed. By following this heuristic, developers are forced to restrict their efforts to the task at hand by only writing the code necessary to accomplish the task without duplication, resulting in a minimization of waste and maximizing of maintainability (by means of minimizing complexity).
Nevertheless, software engineering is both a science and an art. It's a science in that there are rules which govern what can and can't be done to achieve certain ends, but it's also an art in that you get better at it the more you do it and there are definite trade-offs to be made which ultimately must be made subjectively. For example, as a client software developer, I am typically never involved in the design and development of applications which have a short lifespan. As such, I don't wait until I see duplication before introducing convention-based dependency injection into my applications. Introducing the consistent use of dependency injection within an application has a far lower cost at the beginning of a software system's life than it does waiting until you begin to feel the need for it.
With respect to the specific example of adding mapping code in view models, while it does couple the view model to a particular domain model, in practice I wouldn't find this to be that big of an issue. The view model isn't likely to be used with other domain models and the nature of the type of code being introduced (i.e. mapping) doesn't typically contain business logic, so the likelihood of this SRP violation causing significant regression in the system is far less than an SRP violation at the application or domain layers.
That said, I don't find the process of adding mapping logic within constructors to be any sort of significant time saver. If one were to create a separate class to encapsulate the mapping between the domain object and the view model in most languages, we're only talking about an extra few lines of code. Here's the difference in implementation:
So, you're either doing a return new ViewType(domainType), or you're doing a return new ViewTypeMapper().Map(domainType). I just don't see where decoupling in this case adds any significant work. In most cases, you've already wasted your company's or client's time and money by even having a discussion about it because you'll invariably end up talking about it for a longer period of time than if you were to just create separate classes to represent the mappings, or if you were to go ahead and just set up Automapper.
你的朋友是对的。视图应该是愚蠢的,并且不知道有关您的域模型的任何信息。
您是否尝试过使用 Automapper 将您的业务/域实体/模型映射到 dto/viewmodels?
由于评论而提供更多详细信息:
将映射代码放入视图模型违反了关注点分离、SOLID 单一职责原则、MVC 模式和领域驱动设计原则。视图有一项职责,就是将数据传输到屏幕上,仅此而已。恕我直言,没有太多可争论的。这只是一个坏主意,违反了许多核心软件开发原则。
Your friend is right. Views should be dumb and not know anything about your domain model.
Have you tried using Automapper to map your business/domain entities/models to your dto/viewmodels?
More details because of comment:
Putting mapping code in your viewmodels violates the separation of concern, the SOLID single responsibility principal, the MVC pattern, and domain driven design principals. Views have one responsibility, get data to the screen, thats it. IMHO there isn't much to argue about. Its simply a bad idea that violates a lot of core software development principals.