我一直在看 Misko Hevery 的 Google 的干净代码演讲。这些谈话说:在构造函数中请求依赖项,以便其他程序员可以预先准确地看到需要什么来实例化给定对象的实例(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
,他们只需要 mock 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;
}
}
问题
- 由于我没有将我的 DI 容器传递给我的
Customer
对象,它无法创建如上所示的用户对象,因为它没有对数据库 DSN 的引用(并且不应该真的需要知道如何创建用户) - 创建它自己的依赖项使此代码无法测试,因为它们是具体的,没有用于模拟的接缝。
- 如果我确实将容器传递给我的
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 容器中构建吗?