反对使用静态方法的两个主要原因是:
- 使用静态方法的代码很难测试
- 使用静态方法的代码很难扩展
在其他方法中调用静态方法实际上比导入全局变量更糟糕。在 PHP 中,类是全局符号,因此每次调用静态方法时都依赖于全局符号(类名)。这是全局是邪恶的情况。我在 Zend Framework 的某些组件中遇到了这种方法的问题。有些类使用静态方法调用(工厂)来构建对象。我不可能为该实例提供另一个工厂以返回自定义对象。解决这个问题的方法是在程序开始时只使用实例和实例方法,并强制使用单例等。
Miško Hevery,在 Google 担任敏捷教练,有一个有趣的理论,或者更确切地说是建议,我们应该将对象创建时间与我们使用对象的时间分开。所以程序的生命周期一分为二。第一部分(假设是main() 方法),负责处理应用程序中的所有对象连接以及执行实际工作的部分。
所以不要有:
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
我们应该这样做:
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
然后,在索引/主页中,我们会这样做(这是对象连接步骤,或者是创建程序要使用的实例图的时间):
$httpResponseFactory = new HttpResponseFactory;
$httpClient = new HttpClient($httpResponseFactory);
$httpResponse = $httpClient->request();
主要思想是将依赖关系从类中分离出来。这样代码的可扩展性更高,对我来说最重要的部分是可测试的。为什么可测试性更重要?因为我并不总是编写库代码,所以可扩展性并不那么重要,但在我进行重构时,可测试性很重要。无论如何,可测试的代码通常会产生可扩展的代码,所以这并不是一个非此即彼的情况。
Miško Hevery 还明确区分了单例和单例(带或不带大写字母 S)。区别很简单。带有小写“s”的单例由 index/main 中的接线强制执行。您实例化一个类的对象,该对象不实现单例模式,并注意只将该实例传递给需要它的任何其他实例。另一方面,带有大写“S”的 Singleton 是经典(反)模式的实现。基本上是变相的全局,在 PHP 世界中没有多大用处。到目前为止,我还没有看到一个。如果您希望所有类都使用单个数据库连接,最好这样做:
$db = new DbConnection;
$users = new UserCollection($db);
$posts = new PostCollection($db);
$comments = new CommentsCollection($db);
通过执行上述操作,很明显我们有一个单例,并且我们还有一个很好的方法可以在我们的测试中注入一个模拟或存根。令人惊讶的是,单元测试如何导致更好的设计。但是,当您认为测试迫使您考虑使用该代码的方式时,这很有意义。
/**
* An example of a test using PHPUnit. The point is to see how easy it is to
* pass the UserCollection constructor an alternative implementation of
* DbCollection.
*/
class UserCollection extends PHPUnit_Framework_TestCase
{
public function testGetAllComments()
{
$mockedMethods = array('query');
$dbMock = $this->getMock('DbConnection', $mockedMethods);
$dbMock->expects($this->any())
->method('query')
->will($this->returnValue(array('John', 'George')));
$userCollection = new UserCollection($dbMock);
$allUsers = $userCollection->getAll();
$this->assertEquals(array('John', 'George'), $allUsers);
}
}
我使用静态成员(并且我已经使用它们来模仿 PHP 5.3 中的 JavaScript 原型对象)的唯一情况是当我知道各个字段将具有相同的值跨实例时。此时,您可以使用静态属性,也可以使用一对静态 getter/setter 方法。无论如何,不要忘记添加使用实例成员覆盖静态成员的可能性。例如,Zend Framework 使用静态属性来指定Zend_Db_Table 实例中使用的 DB 适配器类的名称。我已经有一段时间没有使用它们了,所以它可能不再相关,但这就是我记得的方式。
不处理静态属性的静态方法应该是函数。 PHP有函数,我们应该使用它们。