在遵守得墨忒耳法的同时,我的对象构造代码应该在哪里?

时间:2011-05-29 09:27:00

标签: dependency-injection law-of-demeter

我一直在看Misko Hevery的Google clean code talks。这些讨论说:在构造函数中请求依赖,因此其他程序员可以预先确切地知道需要什么,来实例化给定对象的实例(law of demeter)。这也使得测试更容易,因为程序员确切地知道需要嘲笑什么。

示例时间
如果我有一个类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)。
  4. 我应该将用户工厂传递给Customer以使其能够构建User个对象吗?

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

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

1 个答案:

答案 0 :(得分:0)

如果我正确地解释这一点,看起来您的问题实际上是关于实体构建和生命周期管理。

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

我故意不试图解释一般的DDD或我提到的概念;关于SO和其他地方可用的材料,有足够的材料。