使用基于MVC的Web框架时,最佳做法是什么?

问题描述 投票:1回答:2

对那些精通基于Web的应用程序开发人员的一些一般性问题。

问题1:

您如何避免“依赖携带”的问题? 据我了解,对象检索的第一步应该发生在控制器的操作方法中。 从那里,您可以使用可能需要某些对象的各种模型,类,服务和组件。

您如何避免仅由于使用的对象需要将对象传递给另一个对象的需要? 我想避免去数据库/缓存再次获取数据,但是我也不想创建需要大量参数的函数。 控制器动作是否应该是您创建最终需要该请求的每个对象的地方?

问题2:

您在会话中存储什么数据? 我的理解是,您通常只应存储用户ID,电子邮件地址,名称和访问权限之类的内容。

如果用户登录时需要针对每个请求分析数据,该怎么办? 您是否应该将整个用户对象存储在缓存中而不是会话中?

问题3:

您是将数据检索方法放置在模型本身中还是在获取数据并返回模型的单独对象中? 这种方法有什么优势?

问题4:

如果您的网站是由用户ID驱动的,那么如何对代码库进行单元测试? 这就是为什么您应该将所有数据检索方法都放在一个集中的位置,以便可以在单元测试中覆盖它吗?

问题5:

一般来说,您是否对控制器进行单元测试? 我听过很多人说这是一个困难甚至不好的做法。 您对此有何看法? 您究竟在控制器中测试什么?

欢迎您分享任何其他有关最佳做法的信息! 我总是愿意学习更多。

model-view-controller design-patterns language-agnostic
2个回答
4
投票
  • 您如何避免“依赖携带”的问题?

良好的面向对象设计的BaseController SuperClass可以处理实例化常用对象等的繁重工作。使用Composite类型在调用之间共享数据是一种不常见的做法。 例如,在Controller中创建您的应用程序特有的上下文对象以在进程之间共享信息并不是一个糟糕的主意。

  • 您在会话中存储什么数据?

尽可能减少人类可能遇到的事情。

如果有一些数据密集型操作需要大量处理开销,而且应用程序经常需要这样做,那么它是会话存储的合适候选者。 是的,对于会话状态,存储诸如用户ID和其他个性化信息之类的信息并不是一个坏习惯。 通常,尽管使用cookie是个性化的首选方法。 永远记住,永远不要相信cookie的内容,例如在信任之前正确验证读取的内容。

  • 您是将数据检索方法放置在模型本身中还是在获取数据并返回模型的单独对象中?

我更喜欢在我的模型中使用Repository模式。 该模型本身通常包含简单的业务规则验证等,而存储库则为结果和转换/操作命中一个业务对象。 市场上有很多Patterns和ORM工具,这是一个激烈争论的话题,因此有时只能归结为对工具等的熟悉。

  • 这种方法有什么优势?

我看到的“存储库模式”的优点是您的模型很笨拙,修改起来更容易。 如果它们代表业务对象(例如Web服务或数据表),则从作为我的MVC应用程序的表示逻辑中充分抽象出对那些基础对象的更改。 如果实现所有逻辑以将模型加载到模型本身中,那么我将违反关注点分离模式。 同样,这都是非常主观的。

  • 如果您的网站是由用户ID驱动的,那么如何对代码库进行单元测试?

强烈建议在代码中尽可能使用依赖注入。 一些IoC容器可以相当有效地处理此问题,一旦被理解,就可以极大地改善您的总体架构和设计。 话虽如此,用户上下文本身应该通过某种形式的已知接口来实现,然后可以在您的应用程序中“模拟”。 然后,您可以在测试工具中模拟所需的任何用户,并且所有相关对象都不知道它们之间的区别,因为它们只是查看界面。

  • 一般来说,您是否对控制器进行单元测试?

绝对。 由于期望控制器返回已知的内容类型,因此使用适当的测试工具,我们可以使用实践来模拟HttpContext信息,调用Action Method并查看结果以确保它们符合我们的期望。 有时,当结果是一些庞大的HTML文档时,这仅导致寻找HTTP状态代码,但是在JSON响应的情况下,我们可以很容易地看到action方法将按预期返回所有方案的信息

  • 您究竟在控制器中测试什么?

控制器的所有公开声明成员均应进行彻底测试。

长问题,长答案。 希望这对任何人都有帮助,请仅将所有这些作为我自己的观点。 这些问题很多都是宗教辩论,您只要练习正确的面向对象设计,SOLID,接口编程,DRY等,始终可以放心。


3
投票

关于依赖关系爆炸, .NET中的“ 依赖关系注入 ”一书(非常出色)解释说,过多的依赖关系表明您的控制器承担了过多的责任,即违反了单一责任原则。 某些责任应抽象到执行多种操作的集合后面。

基本上,您的控制器应该是哑巴 。 如果它需要那么多的依赖关系来完成其工作,那么它就做得太多了! 它应该只接受用户输入(例如URL,查询字符串或POST数据),然后以适当的格式将这些数据传递到您的服务层。

例子,摘自书

我们先从一个OrderService与依赖OrderRepositoryIMessageServiceIBillingSystemIInventoryManagementILocationService 。 它不是控制器,但适用相同的原理。

我们注意到, ILocationServiceIInventoryManagement是一个订单实现算法的两个真正实现细节(使用位置服务找到最接近的仓库,然后管理库存)。 所以我们抽象他们到IOrderFulfillment和具体实施LocationOrderFulfillment使用IInventoryManagementILocationService 。 这很酷,因为我们在OrderService隐藏了一些细节,而且还揭示了一个重要的领域概念:订单履行。 现在,我们可以以基于非位置的方式实现此域概念,而不必更改OrderService ,因为它仅取决于接口。

接下来,我们注意到IMessageServiceIBillingSystem和我们新的IOrderFulfillment抽象实际上都以相同的方式使用:它们被通知有关订单。 因此,我们创建了一个INotificationService ,并使MessageNotification成为INotificationServiceIMessageService的具体实现。 对于BillingNotificationOrderFulfillmentNotification

现在的诀窍是:我们创建一个新的CompositeNotificationService ,它从INotificationService派生并委托给各种“子” INotificationService实例。 我们用来解决原始问题的具体实例将特别委托给MessageNotificationBillingNotificationOrderFulfillmentNotification 。 但是,如果我们希望通知更多系统,则不必去编辑控制器:我们只需要以不同的方式实现特定的CompositeNotificationService

我们OrderService现在只取决于OrderRepositoryINotificationService ,这是更合理! 它有两个构造函数参数,而不是5,而且最重要的是 ,它几乎不负责确定要做什么。

© www.soinside.com 2019 - 2024. All rights reserved.