MVC和仓储模式:控制器、模型和仓库的作用?

9
所以,我一直在研究使用存储库模式作为MVC框架中将持久层与模型解耦的手段。在此之前,我可能会让我的直接调用活动记录方法以存储/检索域对象。
以下是我对应该创建新用户的请求中调用堆栈的构思草图:
这里有我的问题:
1. 这是存储库模式的正确实现吗? 2. 我知道控制器应该从请求中获取用户信息并将其传递到模型中。通常如何处理?控制器是否应该创建一个User对象,然后将其传递到模型中?我肯定不想只将值数组传递到模型中,也不想将15个参数传递给创建用户的模型方法。 3. 为了使该模式真正起作用,看起来我需要一个域对象,它只是一个简单的数据结构,没有行为;如果我使用ORM,则会有一个ORM对象,该对象将描述如何持久化对象。最初,我抵制这种做法,因为它感觉像重复代码,但如果我真的将持久性与业务逻辑分离,那么这是必需的,对吗?例如,如果我选择内存存储,我将不再使用ORM对象。
我的思路是否正确?这是可接受的。请帮助我理清思路。
2个回答

19

1. 这是否是 Repository 设计模式的正确实现?

我不确定你做了哪些研究,但是你搞错了。

为了提供一个真实的例子,说明什么时候以及如何使用 Repository,以下是一个示例:

您正在创建文档管理工具,其中所述文档可以来自多个来源(例如:本地 SQL 数据库、SOAP 服务和缓存)。在这种情况下,您可以创建一个 Repository,它处理“路由”存储。它是应用程序的一部分,负责决定存储/检索每个文档使用哪个数据映射器。

Repository 的目的是将领域逻辑与存储交互分离。对于上述系统,Repository 还将允许添加新的数据源,而无需重写大量代码(如果有的话)。您只需为文档添加另一种类型的映射器即可。

2. 控制器应该创建 User 对象并将其传递到模型中吗?

首先,控制器本身不应创建任何东西。相反,您的控制器应使用工厂获取需要的对象实例。可以通过构造函数或其他方法将此工厂提供给控制器。这称为:依赖注入(要了解更多信息,请观看此讲座)。

另外,如上所述,模型是一个层,而不是任何特定的类或对象。控制器的责任是改变模型层的状态(通过传递数据)。您可以直接在控制器中与领域对象和映射器(或 Repository)交互,但这将意味着在控制器中泄漏一些业务逻辑。建议使用服务,然后操作所述领域对象和存储相关结构。

至于创建新用户帐户时需要10个以上参数的问题,请假设您有一个具有以下特征的操作:

public function postUser( Request $request )
{
    ....
}

如果使用特定的Request实例调用操作,您有两种处理大量参数的选项:

  1. 将实例包装在装饰器中,这样可以让您调用单个方法以形成特定数组中的请求数据。然后将此数组传递给服务。

  2. 在控制器的操作内部形成数组,并在需要数据的地方传递它。

前一种解决方案更适合大型应用程序,在其中需要反复进行此类数据形成。但是在小/中型项目中,第二个选项是常识性的方法。

问题在于,控制器的工作是接收用户的输入,并将其分发到模型层和当前视图。而这种数组的形成恰好符合这种要求。

3.(...)只是一个没有行为的简单数据结构,然后(...)

不是的。领域对象不是“简单数据”。它是应用程序中大多数领域业务逻辑所在的地方。

先忘记神奇的 ORM 吧。实现存储库的第一步是将领域逻辑与存储逻辑分开。领域对象处理验证和业务规则,映射器处理持久性和数据完整性(小例子在这里)。

还有一件事,您必须认识到的是,Web 应用程序的存储库实际上并不会与内存持久性(除了缓存之外)进行交互。相反,您的存储库将管理不同数据源的映射器。


1
控制器应该创建一个用户对象,然后将其传递到模型中吗?
我不确定您所说的“将其传递到模型中”是什么意思——User对象本身就是模型。 “控制器”和“模型”代表设计中的不同层,它们不是特定的对象,并且不应该有单独的UserModel对象,正如您所提到的那样。
存储库接口本身通常被认为是模型的一部分,尽管域对象不应该自己保存——这应该在控制器中完成。
然后,您的控制器的工作将是解释请求并创建一个User对象,然后使用存储库保存用户:
$user = new User(...); // based on Request
$repository->save($user);

“我认为我需要一个领域对象,它只是一个简单的数据结构,没有任何行为。”这不是正确的。您可以并且应该在领域对象中封装行为。至于持久性的实际实现方式,一个好的ORM应该可以处理大部分细节,您不必手动创建额外的类。

当你说“一个好的ORM应该处理大多数细节,你不必手动创建额外的类”时,你是指像这样的东西:“$orm->User->CreateNew()”吗?在我看过的大多数ORM中,当你插入一个新对象时,你首先创建该对象的实例,然后将其添加到集合中。 - Erik Funkenbusch
@MystereMan:我的意思是你不需要一个单独的“ORM对象”。你应该能够直接使用ORM来持久化User对象。 - casablanca
根据领域对象的复杂性,您应该需要一个单独的ORM对象。领域对象!= ORM对象。仓储库处理领域内容并处理领域对象到ORM实体的“转换”以及反之。在简单情况下,您可以直接使用ORM实体。在更复杂的情况下,最好将它们分开,因为很容易用持久性问题破坏领域对象。无论您是否使用ORM,仓储库都会处理细节。 - MikeSW
@MikeSW:确实,你应该让领域对象不涉及持久化问题。我的意思是,你不应该手动编写一个单独的ORM对象类,因为大多数ORM框架都会自动进行映射(例如通过XML配置)。 - casablanca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接