使用静态方法的两个主要缺点是:
- 使用静态方法的代码很难进行测试
- 使用静态方法的代码很难进行扩展
在某个方法内调用静态方法实际上比导入全局变量更糟糕。在PHP中,类是全局符号,因此每次调用静态方法都依赖于全局符号(即类名)。这是全局变量有害的情况之一。我曾经在Zend框架的某个组件中遇到过这种方法的问题。有些类使用静态方法调用(工厂)来构建对象。对我来说,不可能向该实例提供另一个工厂以获取自定义对象。解决这个问题的方法是只使用实例和实例方法,并在程序开始时强制使用单例等。
Miško Hevery是谷歌敏捷教练,他提出了一个有趣的理论,或者说建议,即我们应该将对象创建时间与使用对象的时间分开。因此,程序的生命周期分为两个部分。第一部分(例如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也清楚地区分了单例和Singletons(大小写有或没有"S")之间的差异。区别非常简单。小写"s"的Singleton是由索引/主文件中的连接所强制执行的。您实例化一个不实现Singleton模式的类的对象,并确保只将该实例传递给需要它的任何其他实例。另一方面,具有大写"S"的Singleton是经典(反)模式的实现。基本上是一个伪装成全局变量的东西,在PHP世界中用处不大。到目前为止,我还没有见过这种情况。如果您希望所有类都使用单个DB连接,则最好按以下方式进行:
$db = new DbConnection
$users = new UserCollection($db)
$posts = new PostCollection($db)
$comments = new CommentsCollection($db)
通过上述方法,我们明确了我们拥有一个单例,并且还有一种很好的方式在测试中注入模拟对象或存根。单元测试如何带来更好的设计令人惊讶,但当你考虑到测试强制你思考如何使用代码时,这是有很多道理的。
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有函数,我们应该使用它们。