35

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

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

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

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

示例 1

假设我们想创建一个博客。博客有文章和标签。每篇文章可以有多个标签,每个标签可以属于多篇文章。所以我们在这里有 am: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 调用。

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

4

6 回答 6

30

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

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

于 2012-12-19T12:07:44.680 回答
16

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

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

表示层(模型、视图、布局、模板)和模型层之间的交互应该只通过这些服务发生。

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

于 2012-12-19T19:20:01.320 回答
7

正确的?任何一个。他们都编译,不是吗?

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

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

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

于 2012-12-19T11:46:05.430 回答
7

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

在我看来,将使用的概念取决于所使用的框架(这是实际的观点,下面是更哲学的观点)。如果使用框架,它通常会设置有关如何实现每个组件的规则。例如,我们可以查看不同的 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 应用程序等。因此,它不能以纯粹的形式使用。但无论如何,在我看来,最好的关注点分离可以通过将业务逻辑隔离到模型中,将应用程序逻辑隔离到控制器中来实现。

于 2012-12-19T12:08:17.450 回答
4

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

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

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

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

于 2012-12-19T12:10:13.323 回答
2

是的。它应该。你在谈论领域驱动设计。

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

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

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

于 2019-11-14T17:21:37.767 回答