Answers:
这是一个非常有趣的问题-答案也可能变得有趣^^
考虑事物的最简单方法可能是:
(是的,我承认,真的太过简化了...)
关于静态方法/类的一件事是它们不便于单元测试(至少在PHP中,但在其他语言中也是如此)。
关于静态数据的另一件事是程序中仅存在一个实例:如果您将MyClass :: $ myData设置为某个位置的某个值,则它将在所有位置都具有该值-谈到用户,您将只能拥有一个用户-那不是很好,不是吗?
对于博客系统,我能说什么?实际上,我认为我不会写成静态的。也许是数据库访问类,但最后可能不是
反对使用静态方法的主要原因有两个:
在其他方法中进行静态方法调用实际上比导入全局变量差。在PHP中,类是全局符号,因此每次调用静态方法时,都依赖于全局符号(类名)。这是全球性邪恶的情况。我对Zend Framework的某些组件的这种方法有疑问。有些类使用静态方法调用(工厂)来构建对象。对于我来说,为该实例提供另一个工厂以返回定制的对象是不可能的。解决此问题的方法是仅使用实例和instace方法,并在程序开始时强制执行单例。
在Google担任敏捷教练的MiškoHevery有一个有趣的理论,或者更确切地说,我们应该将对象创建时间与使用对象的时间区分开。因此,程序的生命周期分为两部分。第一部分(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škoHevery还对单身人士和单身人士(带或不带大写字母S)进行了清晰区分。区别很简单。小写字母“ s”的单例由索引/主目录中的接线强制执行。实例化一个不实现Singleton模式的类的对象,并注意仅将实例传递给需要该实例的任何其他实例。另一方面,具有大写字母“ 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
。自从我使用它们已经有一段时间了,所以它可能不再相关,但这就是我记得的方式。
不处理静态属性的静态方法应该是函数。PHP具有功能,我们应该使用它们。
因此在PHP中可以将static应用于函数或变量。非静态变量绑定到类的特定实例。非静态方法作用于类的实例。因此,让我们组成一个名为的类BlogPost
。
title
将是一个非静态成员。它包含该博客文章的标题。我们也可能有一种称为的方法find_related()
。它不是静态的,因为它需要博客文章类的特定实例中的信息。
这个类看起来像这样:
class blog_post {
public $title;
public $my_dao;
public function find_related() {
$this->my_dao->find_all_with_title_words($this->title);
}
}
另一方面,使用静态函数,您可以编写这样的类:
class blog_post_helper {
public static function find_related($blog_post) {
// Do stuff.
}
}
在这种情况下,由于该函数是静态的并且不作用于任何特定的博客文章,因此您必须将博客文章作为参数传递。
从根本上讲,这是有关面向对象设计的问题。类是系统中的名词,作用在它们上的功能是动词。静态函数是过程性的。您传入函数的对象作为参数。
更新:我还要补充说,决策很少是在实例方法和静态方法之间做出的,而更多地是在使用类和使用关联数组之间做出的。例如,在博客应用程序中,您可以从数据库中读取博客文章并将其转换为对象,或者将它们留在结果集中并将其视为关联数组。然后,编写将关联数组或关联数组列表作为参数的函数。
在OO场景中,您在BlogPost
类上编写了对单个帖子起作用的方法,并编写了对帖子集合起作用的静态方法。
在这里,对于大多数答案,我有不同的方法,尤其是在使用PHP时。我认为所有类都应该是静态的,除非您有充分的理由不这样做。一些“为什么不”的原因是:
让我举一个例子。由于每个PHP脚本都生成HTML代码,因此我的框架具有html writer类。这样可以确保没有其他类会尝试编写HTML,因为这是一个专门的任务,应该集中到一个类中。
通常,您将使用html类,如下所示:
html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();
每次调用get_buffer()时,它都会重置所有内容,以便使用html writer的下一个类以已知状态开始。
我所有的静态类都有一个init()函数,该函数在首次使用该类之前需要被调用。按照惯例,这更多是不必要的。
在这种情况下,静态类的替代方法比较麻烦。您不希望每个需要编写少量html的类都必须管理html writer的实例。
现在,我将为您提供一个何时不使用静态类的示例。我的表单类管理一个表单元素列表,例如文本输入,下拉列表等。通常这样使用:
$form = new form(stuff here);
$form->add(new text(stuff here));
$form->add(new submit(stuff here));
$form->render(); // Creates the entire form using the html class
使用静态类无法做到这一点,尤其是考虑到每个添加的类的某些构造函数都需要做很多工作。而且,所有元素的继承链都非常复杂。因此,这是一个明确的示例,其中不应使用静态类。
大多数实用程序类(例如转换/格式化字符串的类)都是静态类的不错的选择。我的规则很简单:PHP中的所有内容都是静态的,除非有一个原因不应这样做。
“在其他方法中进行静态方法调用实际上比导入全局变量更糟。” (定义“更糟”)...和“不处理静态属性的静态方法应该是函数”。
这些都是详尽的陈述。如果我有一组与主题相关的功能,但是实例数据是完全不合适的,那么我宁愿将它们定义在一个类中,而不是将每个定义在全局名称空间中。我只是使用PHP5中提供的机制来
这完全是增强较高的内聚力和较低的耦合度的便捷方法。
FWIW-至少在PHP5中没有“静态类”这样的东西;方法和属性可以是静态的。为了防止实例化该类,也可以将其声明为抽象。
首先问问自己,这个对象将代表什么?对象实例适合于对单独的动态数据集进行操作。
一个很好的例子是ORM或数据库抽象层。您可能有多个数据库连接。
$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));
这两个连接现在可以独立运行:
$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);
现在在此程序包/库中,可能还有其他类,例如Db_Row等,以表示从SELECT语句返回的特定行。如果此Db_Row类是静态类,则将假定您在一个数据库中只有一行数据,并且不可能做对象实例可以做的事情。对于实例,您现在可以在无限数量的数据库中的无限数量的表中具有无限数量的行。唯一的限制是服务器硬件;)。
例如,如果Db对象上的getRows方法返回Db_Row对象的数组,那么您现在可以彼此独立地对每一行进行操作:
foreach ($someRecordsFromDb1 as $row) {
// change some values
$row->someFieldValue = 'I am the value for someFieldValue';
$row->anotherDbField = 1;
// now save that record/row
$row->save();
}
foreach ($someRecordsFromDb2 as $row) {
// delete a row
$row->delete();
}
静态类的一个很好的例子是处理注册表变量或会话变量的东西,因为每个用户只有一个注册表或一个会话。
在您的应用程序的一部分中:
Session::set('someVar', 'toThisValue');
在另一部分中:
Session::get('someVar'); // returns 'toThisValue'
由于每个会话一次只能有一个用户,因此没有必要为该会话创建实例。
我希望这会有所帮助,以及其他有助于清除问题的答案。作为附带说明,请检查“ 内聚 ”和“ 耦合 ”。它们概述了在编写适用于所有编程语言的代码时要使用的一些非常非常好的做法。
通常,除非绝对必须在所有实例之间共享它,或者除非要创建单例,否则应使用成员变量和成员函数。使用成员数据和成员函数可以使您的函数重用于多个不同的数据段,而如果使用静态数据和函数,则只能拥有一个数据副本用于操作。另外,尽管不适用于PHP,但是静态函数和数据导致代码不可重入,而类数据可促进重入。
我想说的是,在跨语言应用程序中肯定有需要静态变量的情况。您可能有一个将语言传递给的类(例如$ _SESSION ['language']),并且该类又可以访问其他设计如下的类:
Srings.php //The main class to access
StringsENUS.php //English/US
StringsESAR.php //Spanish/Argentina
//...etc
使用Strings :: getString(“ somestring”)是从您的应用程序中提取语言用法的好方法。您可以随意执行此操作,但是在这种情况下,让每个字符串文件都具有带有Strings类访问的字符串值的常量,效果很好。