在尊重德米特定律的同时,我的对象构造代码应该在哪里?

发布于 2024-11-10 11:14:02 字数 2429 浏览 0 评论 0 原文

我一直在观看由 Misko Hevery 主持的 Google 简洁代码讲座。这些演讲说:在构造函数中请求依赖项,以便其他程序员可以准确地看到预先需要什么来实例化给定对象的实例(迪米特定律)。这也使得测试变得更容易,因为程序员确切地知道需要模拟什么。

示例时间
如果我有一个 Customer 类,并且还有一个 CustomerDAO 类来抽象数据访问。当我构造客户对象时,我可能会执行以下操作:

database = new Database('dsn');
customerDao = new CustomerDAO(database);
customer = new Customer(customerDao);

这可能会发生在我的控制器中。我可以通过使用依赖项注入容器来简化此对象构造。下面我使用 DI 容器来获取数据库类的实例,因为它在我的应用程序中广泛使用。这将构造代码减少到一处,并且可以模拟进行测试。

我是否应该将域类依赖项(在本例中为 DAO 对象)添加到我的 DI 容器中?如果我的应用程序很大,这会让我的 DI 容器变得很大吗?

使用 DI 容器,我的代码可能如下所示:

// container setup
container->dsn = '...';
container->dbh = function($c) {
    return new Database($c->dsn);
};
container->customerDao = function($c) {
    return new CustomerDAO($c->dbh);
};

// controller code
class ControllerCustomer extends ControllerBase {

    public function index() {
        container = this->getContainer();
        customer = new Customer(container->customerDao);
        view->customerName = customer->getName();
        view->render();
    }

}

似乎没问题,如果另一个程序员想要测试 Customer,他们只需要模拟 CustomerDAO

更进一步来看这个例子,如果我有依赖于其他域类的域类,那么我的 DI 容器肯定不需要知道如何构造每个域类?例如:

我的客户可能是一家公司/机构,因此拥有许多用户。

class Customer {

  protected _dao;

  public function Customer(dao) {
    _dao = dao;
  }

  public function listUsers() {
    userIds = _dao->getAllUserIds();
    users = array();
    foreach (userIds as uid) {
      user = new User(new UserDAO(new Database('dsn')); // problem
      users[] user->load(uid);
    }
    return users;
  }

}

问题

  1. 由于我没有将 DI 容器传递给我的 Customer 对象,因此它无法创建如上所示的用户对象,因为它没有对数据库 DSN 的引用(并且实际上不需要知道如何让用户)
  2. 创建自己的依赖项使得此代码无法测试,因为它们是具体的,没有用于模拟的接缝。
  3. 如果我确实将容器传递给我的 Customer 类,这是否会使我的 Customer 接口变得谎言? (参见链接的 Google 视频中的 9:15)。

我是否应该将用户工厂传递给 Customer 以使其能够构造 User 对象?

database = new Database('dsn');
userDao = new UserDAO(database);
userFactory = new UserFactory(userDao);
customer = new Customer(customerDao, userFactory);

UserFactory 的构造应该在我的 DI 容器中吗?

I've been watching Google's clean code talks by Misko Hevery. These talks say: ask for dependencies in the constructor, so other programmers can see exactly what is needed up front, to instantiate an instance of a given object (law of demeter). This also makes testing easier as a programmer knows exactly what needs to be mocked.

Example time
If I have a class Customer and I also have a CustomerDAO class to abstract data access away. When I construct a customer object I might do the following:

database = new Database('dsn');
customerDao = new CustomerDAO(database);
customer = new Customer(customerDao);

This might happen in my controller. I can simplify this object construction via use of a dependency injection container. Below I've used a DI container to obtain an instance of my database class, as that is widely used throughout my application. This reduces the construction code to one place and can be mocked for testing.

Should I be adding my domain class dependencies (in this case DAO objects) to my DI container? If my application is large, will this make my DI container huge?

Using a DI container my code might look like this:

// container setup
container->dsn = '...';
container->dbh = function($c) {
    return new Database($c->dsn);
};
container->customerDao = function($c) {
    return new CustomerDAO($c->dbh);
};

// controller code
class ControllerCustomer extends ControllerBase {

    public function index() {
        container = this->getContainer();
        customer = new Customer(container->customerDao);
        view->customerName = customer->getName();
        view->render();
    }

}

Seems to be OK, if another programmer wants to test Customer, they need only mock CustomerDAO.

Taking this example a step further, if I have domain classes with dependencies on other domain classes, surely my DI container should not need to know how to construct every domain class? For example:

My customer might be a company/institution and therefore have many users.

class Customer {

  protected _dao;

  public function Customer(dao) {
    _dao = dao;
  }

  public function listUsers() {
    userIds = _dao->getAllUserIds();
    users = array();
    foreach (userIds as uid) {
      user = new User(new UserDAO(new Database('dsn')); // problem
      users[] user->load(uid);
    }
    return users;
  }

}

Problems

  1. As I've not passed my DI container to my Customer object, it can't create user objects as shown above as it has no reference to the database DSN (and shouldn't really need to know how to make users)
  2. Creating it's own dependencies makes this code untestable as they're concrete with no seams for mocking.
  3. If I do pass the container to my Customer class, does this make my interface for Customer lie? (See 9:15 in the linked Google video).

Should I be passing a user factory to Customer to enable it to construct User objects?

database = new Database('dsn');
userDao = new UserDAO(database);
userFactory = new UserFactory(userDao);
customer = new Customer(customerDao, userFactory);

Should the construction for UserFactory be in my DI container?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

羁绊已千年 2024-11-17 11:14:02

如果我的解释正确,那么您的问题实际上是关于实体构建和生命周期管理的。

DDD 是一种设计方法,它为如何解决此类问题提供了非常规范的指导;在您的情况下,相关概念是存储库和聚合根。虽然 DDD 可能不会直接回答您的问题,但它会让您更轻松地提出符合您要求的基于模式的解决方案。

我故意不尝试一般性地解释 DDD 或我提到的概念; SO 和其他地方有足够的材料。

If I am interpreting this correctly, it seems like your question is actually about entity construction and lifecycle management.

DDD is one design approach which provides very prescriptive guidance on how to approach problems like these; in your case the relevant concepts are repositories and aggregate roots. While DDD probably won't answer your question directly, it will make it much easier for you to come up with a pattern-based solution which matches your requirements.

I'm purposely not attempting to explain DDD in general or the concepts I mentioned; there is enough material about that available on SO and elsewhere.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文