代码之家  ›  专栏  ›  技术社区  ›  Mike Hofer

MVC、DI和模拟对象

  •  2
  • Mike Hofer  · 技术社区  · 15 年前

    我正努力用同样多的方法来实现MVC 有用的 为了自动化单元测试的目的,尽可能地抽象。在这个过程中,我遇到了一个有趣的难题:如何在哪里为我的数据库声明模拟对象?

    这是我的东西。

    • 接触视图 是一个实现 ICOCTA视图 . 它知道 ICOCTACT模型 以便它可以响应来自模型对象的事件通知而更新自身。
    • 接触 是一个实现的类 ICOCTACT模型 . 它封装了操作对象的业务规则,以及从数据访问层中获取/更新数据的代码。
    • 接触控制器 是一个实现 IContactController(IContactController) 并且对两者都有了解 ICOCTACT模型 ICOCTA视图 .
    • 数据库 是一个实现 数据库接口 并包含用于选择、插入、更新和删除数据库中数据的方法。

    对我来说,这是个棘手的问题。模型对象需要知道如何与 数据库接口 这样它就可以在真实的数据库或模拟对象上操作。我面临的问题是 如何提供参考 数据库 不违反分离关系。

    我宁愿视图和控制器不知道数据是如何存储的。这样,如果我以后选择这样做,我可以将IDatabase换成类似ijsonStore或ixmlstore的东西,只需要接触模型类。我不希望视图和控制器对数据存储的位置和方式进行任何假设。

    我看到了一些可能的解决方案,但我不确定最好的是什么。

    • 我可以声明一个公开公共财产的单一实体, 数据库 (类型) 数据库接口 )通过这种方式,单元测试可以将数据库设置为模拟对象,而生产代码则将其设置为生产数据库。但这意味着生产代码,在某个时候,必须知道关于idatabase。我想那是不可避免的,但我希望有其他更好的解决办法。
    • 我可以修改模型类来维护对数据库的引用,并将其引入构造函数中。这似乎是不可取的,因为它会导致许多额外的代码。然后,我又回到了正题1:谁声明模型实例就必须知道 数据库接口 我要使用的对象。

    我相信有其他的选择,我只是不知道。所以我把这个扔到外面去了:你们会怎么做?你们看到什么效果很好?

    事先谢谢。

    3 回复  |  直到 15 年前
        1
  •  1
  •   Mark Seemann    15 年前

    通常最好将模型对象保持为POCO/POJO,并让控制器使用注入的依赖项填充模型(和视图)。

    因为很多不同的原因, 构造器注入 是DI的最佳默认选择。下面是一个基于C的控制器示例,其中包含一个注入的IDatabase:

    public class ContactController : IContactController
    {
        private readonly IDatabase db;
    
        public ContactController(IDatabase db)
        {
            if (db == null)
            {
                throw new ArgumentNullException("db");
            }
    
            this.db = db;
        }
    
        public IContactView CreateView(int id)
        {
            var model = this.db.Select(id);
            return new ContactView(model);
        }
    }
    

    我不知道这是否看起来像您现有的接口,但它应该足够给您一个想法。注意如何 readonly 关键字和guard子句协作,使注入的依赖项成为contactController的不变量,从而确保其余代码始终存在。

    你可以使用 穷人的迪 或者一个合适的DI容器,将应用程序连接到应用程序的入口点。这将是您将IDatabase映射到具体实现的地方,允许您遵循 Liskov Substitution Principle 在代码的其余部分。

        2
  •  1
  •   Steve Freeman    15 年前

    正如您指出的,您必须将持久性连接到模型。如果您认为这是一个依赖项(没有它,模型就不能工作),那么将它传递给构造函数。这告诉全世界,一个模型必须能够访问持久性,并且在您拥有它所需要的一切之前,不能创建一个模型。一个构造函数看起来不像是比一个属性更多的代码,而且更安全。

    第二,我不会说那东西是 IDatabase . 单词 数据库 描述实现,而不是角色。根据你的领域,我可以称之为 AddressBook Rolodex 或者告诉我有关应用程序上下文的信息。这使得模型代码没有任何技术实现细节。如果我使用一个数据库来实现它,那么我可以调用该类 DatabaseAddressBook . 我通常发现对这种分离的挑剔会产生更清晰的代码和(有时)更清晰的思考。

        3
  •  0
  •   Rogério    15 年前

    我用最简单的方法来解决这类问题:通过 Database 甲类 静态立面 (不需要实现单独的Java接口)。

    这个解决方案不仅使生产代码尽可能简单,而且还可以进行单元测试!例如,这里有一个几乎完整的JUnit测试,使用 JMockit 期望API(我为实现此类测试而创建的工具):

    public final class Database
    {
        private Database() {}
    
        public static void save(Object transientEntity) { ...uses JPA/JDO... }
        ... other static methods ...
    }
    
    public class Contact
    {
        public Contact(String s, int i) { ... }
    
        public void doSomeBusinessOperation()
        {
            ...
            Database.save(<some transient entity>);
            ...
        }
    }
    
    public final class ContactTest
    {
        @Test
        public void doSomeBusinessOperationShouldSaveEntityToDatabase()
        {
            new Expectations()
            {
                Database db; // a mock field, causing "Database" to be mocked
    
                {
                    // Records an expectation for the method to be called once:
                    Database.save(any);
                }
            };
    
            // Exercises SUT, which should replay the expectation as recorded:
            new Contact("abc", 123).doSomeBusinessOperation();
        }
    }
    
    推荐文章