是或否:MVC 中的模型是不是应该包含应用程序逻辑?

Posted

技术标签:

【中文标题】是或否:MVC 中的模型是不是应该包含应用程序逻辑?【英文标题】:Yes or no: Should models in MVC contain application logic?是或否:MVC 中的模型是否应该包含应用程序逻辑? 【发布时间】:2012-12-06 17:59:37 【问题描述】:

昨天我与我们的一位开发人员就 MVC 进行了一些讨论,更准确地说是关于模型组件在 MVC 中的作用。

在我看来,模型应该只包含属性而几乎没有功能,因此模型类中的方法越少越好。

尽管我的同事认为模型可以而且应该具有更多功能并提供更多功能。

这是我们争论过的一个例子。

示例 1

假设我们想创建一个博客。博客有文章和标签。每篇文章可以有多个标签,每个标签可以属于多篇文章。所以我们这里有一个 m:n 关系。

在伪代码中它可能看起来像这样:

class Article
    public int id;
    public String title;
    public String content;
    public Tag[] tags;

    // Constructor
    public void Article(id, title, content, tags)
        this.id = id;
        this.title = title;
        this.content = content;
        this.tags = tags;
    


class Tag
    public int id;
    public String name;

    // Constructor
    public Tag(id, name)
        this.id = id;
        this.name = name;
    

现在,假设我们在这里工作的是松散耦合,这意味着我们可能有一个没有标签的 Article 实例,所以我们将使用 Ajax 调用(对我们的后端有一个包含所有信息)来获取属于我们文章的标签。

这里是棘手的部分。我相信通过 Ajax+JSON 获取后端数据应该是控制器的工作,它使用一个专用类来处理使用解析器的 ajax 请求:

class MyController
    private void whatever(articleID)
        Article article = (Article) ContentParser.get(articleID, ContentType.ARTICLE);
        doSomethingWith(article);
    


public abstract class ContentParser
    public static Object get(int id, ContentType type)
        String json = AjaxUtil.getContent(id, type.toString()); // Asks the backend to get the article via JSON
        Article article = json2Article(json);

        // Just in case
        Tag[] tags = article.tags;
        if (tags == null || tags.length <= 0)
            json = AjaxUtil.getContent(article.id, ContentType.TAGS); // Gets all tags for this article from backend via ajax
            tags = json2Tags(json);
            article.tags = tags;
        

        return article;
    

    // Does funky magic and parses the JSON string. Then creates a new instance of Article
    public static Article json2Article(String json)
        /*
         ...
        */
        return new Article(id, title, content, tags);
    

    // Does funky magic and parses the JSON string. Then creates a new instance of Tag
    public static Tag[] json2Tags(String json)
        /*
         ...
        */
        return tags;
    


示例 2

我的同事认为这与 MVC 的想法不符,他建议模型应该注意这一点:

class Blog
    public int id;
    public String title;
    public Article[] articles;

    // Constructor
    public Blog(id, title, articles)
        this.id = id;
        this.title = title;
        this.articles = articles;
    

    public void getArticles()
        if (articles == null || articles.length <= 0)
            String json = AjaxUtil.getContent(id, ContentType.ARTICLE); // Gets all articles for this blog from backend via ajax
            articles = json2Articles(json);
        
        return articles;
    

    private Article[] json2Articles(String json)
        /*
         ...
        */
        return articles;
    



class Article
    public int id;
    public String title;
    public String content;
    public Tag[] tags;

    // Constructor
    public Article(id, title, content, tags)
        this.title = title;
        this.content = content;
        this.tags = tags;
    

    public Tag[] getTags()
        if (tags == null || tags.length <= 0)
            String json = AjaxUtil.getContent(id, ContentType.TAGS); // Gets all tags for this article from backend via ajax
            tags = json2Tags;
        
        return tags;
    

    // Does funky magic and parses the JSON string. Then creates a new instance of Tag
    private Tag[] json2Tags(String json)
        /*
         ...
        */
        return tags;
    

在模型之外,您可以:blog.getArticles();article.getTags(); 来获取标签,而无需打扰 ajax 调用。

然而,尽管这可能很方便,但我相信这种方法与 MVC 不符,因为到最后,所有模型都将充满各种方法,这些方法可以做各种时髦的事情,而控制器和辅助类几乎什么都不做。

在我对 MVC 的理解中,模型中应该只包含属性和最少的“辅助方法”。例如,模型“Article”可以提供方法 getNumOfTags(),但它不应该自己进行任何 Ajax 调用。

那么,哪种方法是正确的?

【问题讨论】:

我知道这是一个老问题,但只是把我的两分钱放在那里。模型不应包含数据持久性或检索逻辑。这就是我们拥有存储库层的目的。他们不应该有业务逻辑。这就是业务层的用途。它们可能也不应该有辅助方法,因为辅助方法通常在整个应用程序中使用,而不是特定于特定模型。验证逻辑位于前端以提供更好的用户体验,并在业务层进行复制以满足业务逻辑需求。那么我们现在还剩下什么可以放入模型中呢? :) 有人更好地回答这个人(@user20358)!作为一个 MVC 新手,我很好奇 :) 关注的话题和答案真的很好。我要感谢你们所有人。另外,我想给一个小提示。能够维护现有的源代码本地后果是至关重要的;因此,在这一点上,将数据和逻辑放在一起至关重要。如果你有足够的时间,你可以阅读 Kent Beck 的实施模式一书。他经常提到这些行为。此外,Martin Fowler 有一些关于 GUI 架构模式的精彩博文。 【参考方案1】:

一般来说,我也尽量让控制器在逻辑上保持简单。如果需要业务逻辑,它将上升到“服务层”类来处理它。这也节省了重复任何代码/逻辑,如果业务逻辑发生变化,这最终会使整个项目更易于维护。我只是将模型纯粹保留为实体对象。

我认为上面的答案很好地总结了这一点,很容易根据设计模式过度设计项目:选择适合您并且最可维护/最高效的任何东西。

【讨论】:

嗯,过度设计很容易在较小的项目中发生,但项目越大,就越需要规划。在一个较旧的 Java 项目中,我在模型中有很多业务逻辑,这会导致巨大的模型类文件。现在我在一个 javascript 项目中,我将模型用作简单的“值对象”,没有任何逻辑,并在我的控制器中完成所有“时髦的编码”。这再次导致巨大的控制器类。在控制器和模型之间有一个额外的服务层听起来像是一个保持两者清洁的好主意。 是的,将服务层从控制器和模型中抽象出来是前进的方向。它只是使项目更具可读性,并且作为开发人员也更容易浏览。如前所述,它也避免了重复,如果您有多个控制器、项目等,使用与服务层相同的方法和逻辑进行数据检索和操作,这将是一个巨大的好处。希望对您有所帮助! 我认为“上面的答案很好地总结了它”不再适用。你现在是最佳答案!【参考方案2】:

您应该停止将 MVC 中的“模型”视为某些类。模型不是类或对象。模型是一个层(在现代 MVC 中,自概念出现以来已经发生了一些演变)。人们倾向于称之为“模型”的实际上是domain object(我将这种大规模的愚蠢归咎于 Rails)。

应用逻辑(领域逻辑结构和存储抽象之间的交互)应该是模型层的一部分。更准确地说:它应该在Services 内。

表示层(模型、视图、布局、模板)和模型层之间的交互只能通过这些服务进行。

应用程序在控制器中没有位置。控制器是表示层的结构,它们负责处理用户输入。请不要将 deomain 对象暴露给它。

【讨论】:

指责 Rails 的事情让我发笑。 +1 我(认为)在指责 CodeIgniter。我刚开始使用它,我相信它会误导我。在v3 中,一个模型似乎代表了一个表(插入、选择、更新、删除行)、一行(对象属性=表字段);这是一堂课。在v4 中,他们引入了表示行的实体。所以现在有模型(表)和实体(行)。【参考方案3】:

对吗?任何一个。他们都编译,不是吗?

方便的技巧很好,为什么不使用它们呢?话虽如此,正如您所指出的,如果您将各种逻辑放入其中,您可能会得到臃肿的模型。但是,同样地,当控制器在每个动作中执行大量操作时,您可能会得到臃肿的控制器。如果有必要,也有办法从中提取元素。

归根结底,所有设计模式都是指南。你不应该盲目地遵循任何规则,只是因为别人说了。做对你有用的事,你认为可以提供干净、可扩展的代码并达到认为是好代码的任何指标。

话虽如此,对于真正的理想主义 MVC,我想说模型不应该有任何外部动作,它们是数据表示,仅此而已。但请随意不同意:-)

【讨论】:

+1 表示“归根结底,所有设计模式都是指导方针”。如果您担心模型臃肿,请提取对 DAO 的数据访问 - 根据定义,数据访问对象既不是模型也不是控制器。 @Zecc 是的,DAO、服务类、ViewModel 等。有很多方法可以从 MVC 架构的各个部分抽象逻辑。人们常常热衷于遵循模式的word,而不是他们的精神【参考方案4】:

您对模块的建议(内部没有任何业务逻辑)听起来更像是在谈论值对象。你们大学的建议听起来更像是域对象。

在我看来,将使用的概念取决于所使用的框架(这是实际的观点,下面是更哲学的观点)。如果使用框架,它通常会设置有关如何实现每个组件的规则。 例如,我们可以查看不同的 MVC 框架。在 Flex 的Cairngorm 框架中,我们两者都有。 VO(值对象)主要用于绑定到视图,而 DO(域对象)保存业务逻辑。如果我们查看 ASP.NET 的 MVC 实现,我们有一个模型,它至少包含数据 (VO),但也包含一些验证(如果需要)。让我们看一个 UI MV* 框架 - 例如 Backbone.js。 Backbone 的文档说:

模型是任何 JavaScript 应用程序的核心,包含 交互式数据以及围绕它的大部分逻辑: 转换、验证、计算属性和访问控制。

如果我们查看 Smalltalk 提供的传统 MVC,我们会看到:“模型:管理应用程序域的行为和数据”,因此我们在其中包含一些行为,而不仅仅是普通数据。

让我们实际考虑一下,如果模型中没有任何逻辑,我们可能应该将所有应用程序和业务逻辑放入控制器中。

现在让我们关注一个具体的例子。想象一下,我们有一个模型,它是一个 Graph。我们想找到其中两个节点之间的最短路径。一个很好的问题是把找到最短路径的算法放在哪里?这是一种业务逻辑,对吧?如果我们看看 MVC 的主要好处(代码重用、DRY 等),我们可以看到,如果我们想以最好的方式重用我们的模型,我们应该在其中实现最短路径。最短路径算法通常取决于图内部表示(或至少为了算法的最佳性能),但这种表示被封装到模型中,不幸的是我们不能重用矩阵表示和邻居列表的完整最短路径,所以它不是将其放入控制器中是个好主意。

因此,作为结论,我可以说这取决于您的需求(大部分)。传统的 MVC 目的是在 UI 中使用(在 GoF 内

模型/视图/控制器 (MVC) 三元组 [首先由 Krasner 和 Pope 在 >1988 年描述] 用于在 Smalltalk-80 中构建用户界面。

)

现在我们在不同的领域使用它 - 仅 UI,用于 Web 应用程序等。因此,它不能以纯粹的形式使用。 但无论如何,在我看来,最好的关注点分离可以通过将业务逻辑隔离到模型中,将应用程序逻辑隔离到控制器中来实现。

【讨论】:

通过依赖注入,你可以将你的服务注入你的控制器,然后让你的控制器作为纯粹的东西来接收请求并返回结果。它不涉及业务......您的服务可以。所以你的应用程序由你的视图模型、视图、控制器、服务层和你的业务层组成(......以及你需要的任何其他东西) 在 MVC 中没有 View Model 的概念。在传统的 MVC 中也没有 Service 的概念。 ViewModel 是 MVVM 架构设计模式的一部分。带有服务的 MVC 通常称为 MVCS。当我阅读这个问题时,它被问到关于 MVC,而不是 MVVM 或 MVCS。 @MinkoGechev 为什么不呢? ViewModel 只是另一种工具,没有理由不能在 MVC 中使用它们。模式不是单一的独立结构。混搭,汲取灵感,做你认为有效的事! 当然混合模式很好。 MVC 架构设计模式也是 Strategy、Observer 和 Composite 的混合。当我们开始混合像 MVVM 和 MVC 这样的架构模式时,我们失去了它们的本质。当我们混合模式时,我们没有严格的分类。我读到的问题是关于 MVC 而不是 MVVM 或 MVC/MVVM 之间的混合【参考方案5】:

简而言之,我认为模型应该只是将发送到您的视图的数据。它有助于将 MVC 范式引入应用程序的其他方面。

如果您不想破坏 MVC 模式,您的数据应该全部作为业务模型返回到您的控制器并解压缩到您的 ViewModel 中。请求信息服务器端,然后发送所有内容。如果您需要发出 JSon 请求,那么这应该是 Rest Service 或对 Controller 的调用。拥有这些 getTags 和 getArticles 会变得非常混乱......如果您的视图现在决定调用哪个......我无法理解为什么您没有预先提供这些信息。使用静态方法是相同的方法,只是角度不同。

我发现最好让我的控制器操作调用一个注入服务,该服务会发挥作用,并使用 MVC Web 应用程序中的模型来返回信息。这使事情变得更整洁,并且进一步强调了关注点的分离。然后,您的 Controller Actions 变得非常精简,并且很清楚它们在做什么。

我相信,从将模型视为完全愚蠢的开始,可能会对我从这段代码中看到的一些架构问题进行分类。

【讨论】:

【参考方案6】:

是的。这应该。你说的是领域驱动设计。

https://en.wikipedia.org/wiki/Domain-driven_design

如果你觉得你没有这样做,那么你就是在做贫血领域模型设计。那是一个反模式。

我阅读了 Martin Flower 的一篇关于贫血域设计有多糟糕的文章。 https://martinfowler.com/bliki/AnemicDomainModel.html

【讨论】:

我完全同意福勒的观点。 Wikipedia MVC page 上的简洁描述是,IMO,正确。

以上是关于是或否:MVC 中的模型是不是应该包含应用程序逻辑?的主要内容,如果未能解决你的问题,请参考以下文章

如何将输出作为是或否而不是阈值?

如果嵌套查询在 SQL Server 中是不是有结果,如何返回是或否?

持久数据库连接 - 是或否?

MVC 3 - 控制器和视图模型 - 哪个应该包含大部分业务逻辑?

JOptionPane 是或否窗口

感知机