何时使用静态类与实例化类

PHP 是我的第一种编程语言。我不知道什么时候该使用静态类,什么时候该使用实例化对象。

我意识到你可以复制和克隆对象。然而,在我使用 php 的所有时间中,任何对象或函数最终总是作为单个返回值(array、 string、 int)或 void。

我理解书中的概念,比如电子游戏角色课。复制汽车物体,并使新的一个红色,这一切都是有意义的,但什么不是它的应用程序在 PHP 和网络应用程序。

举个简单的例子。博客。博客的哪些对象最好实现为静态对象或实例化对象?DB 类?为什么不在全局范围内实例化 db 对象呢?为什么不让每个对象都是静态的呢?那表演呢?

这只是风格吗? 有没有合适的方法来做这些事情?

55665 次浏览

一般来说,您应该使用成员变量和成员函数,除非它必须在所有实例之间共享,或者您正在创建一个单例。使用成员数据和成员函数允许您为多个不同的数据块重用函数,而如果使用静态数据和函数,则只能有一个操作数据副本。此外,尽管静态函数和数据不适用于 PHP,但它们会导致代码不可重入,而类数据有助于实现可重入。

因此在 PHP 中静态可以应用于函数或变量。非静态变量绑定到类的特定实例。非静态方法作用于类的实例。那么让我们创建一个名为 BlogPost的类。

title将是非静态成员。它包含了那篇博文的标题。我们可能还有一个名为 find_related()的方法。它不是静态的,因为它需要来自 blog post 类的特定实例的信息。

这个类应该是这样的:

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.
}
}

在这种情况下,由于函数是静态的,并且不作用于任何特定的 blog 帖子,因此必须将 blog 帖子作为参数传递。

从根本上说,这是一个关于面向对象设计的问题。您的类是系统中的名词,作用于它们的函数是动词。静态函数是过程性的。将函数的对象作为参数传入。


更新: 我还要补充的是,很少在实例方法和静态方法之间做出决定,更多的是在使用类和使用关联数组之间做出决定。例如,在一个博客应用程序中,您要么从数据库中读取博客文章并将它们转换为对象,要么将它们保留在结果集中并将它们视为关联数组。然后编写以关联数组或关联数组列表作为参数的函数。

在 OO 场景中,您在 BlogPost类上编写作用于单个帖子的方法,并编写作用于帖子集合的静态方法。

这是一个相当有趣的问题——答案可能也会变得有趣 ^ ^

考虑问题最简单的方法可能是:

  • 使用一个实例化的类,其中每个对象都有自己的数据(就像用户有名称一样)
  • 当静态类只是一个工具时,使用它来处理其他东西(例如,BB 代码到 HTML 的语法转换器; 它没有自己的生命)

(是的,我承认,真的是过于简单化了...)

静态方法/类的一个特点是它们不便于单元测试(至少在 PHP 中是这样,但在其他语言中可能也是如此)。

关于静态数据的另一件事情是,在你的程序中只有一个实例存在: 如果你将 MyClass: : $myData 设置为某个值,它就会有这个值,并且在任何地方都只有它——说到用户,你只能有一个用户——这并不是很好,不是吗?

对于一个博客系统,我能说什么呢?实际上,我认为没有多少是静态的; 也许是 DB-access 类,但最后可能不是 ^ ^

这一切都只是风格吗?

很远,是的。您可以在不使用静态成员的情况下编写完美的面向对象程序。事实上,有些人会争辩说,静态成员首先是一种杂质。我建议——作为 Oop 的初学者——尽量避免所有的静态成员。它会迫使你写的方向是 面向对象而不是 程序上的风格。

反对使用静态方法的两个主要原因是:

  • 代码使用静态方法是很难的 测试
  • 代码使用静态方法是很难的 延伸

在其他方法中调用静态方法实际上比导入全局变量更糟糕。在 PHP 中,类是全局符号,因此每次调用静态方法时都依赖于全局符号(类名)。这是一个全局是邪恶的例子。我在使用 Zend 框架的某个组件时遇到了这种方法的问题。有些类使用静态方法调用(工厂)来构建对象。我不可能为那个实例提供另一个工厂来获得返回的定制对象。解决这个问题的方法是只使用实例和实例方法,并在程序开始时强制使用单例或类似的方法。

Mi ko Hvery 是 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();
}
}

然后,在 index/main 页面中,我们将这样做(这是对象连接步骤,或者是创建程序使用的实例图的时间) :

$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);

通过执行上面的操作,很明显我们有了一个单例模式,而且我们也有了一个在测试中注入 mock 或存根的好方法。令人惊讶的是单元测试如何导致更好的设计。但是,当您认为测试迫使您思考如何使用该代码时,这是非常有意义的。

/**
* 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 有函数,我们应该使用它们。

“在其他方法中调用静态方法实际上比导入全局变量更糟糕。”(定义“更糟糕”) ... 和“不处理静态属性的静态方法应该是函数”。

这些都是非常笼统的陈述。如果我有一组与主题相关的函数,但实例数据是完全不适当的,那么我宁愿在类中定义它们,而不是在全局名称空间中定义每个函数。我只是使用 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'

由于每个会话一次只有一个用户,因此没有必要为 Session 创建实例。

我希望这个和其他的答案能够帮助我们理清思路。另外,请看“ 凝聚力”和“ 耦合”。它们概述了在编写适用于所有编程语言的代码时可以使用的一些非常、非常好的实践。

如果你的类是静态的,这意味着你不能将它的对象传递给其他类(因为没有实例可能) ,这意味着你的所有类都将直接使用这个静态类,这意味着你的代码现在与这个类紧密耦合。

紧密耦合使您的代码更不可重用、更脆弱并且容易出错。您希望避免静态类,以便能够将类的实例传递给其他类。

是的,这只是许多其他原因中的一个,其中一些已经被提到了。

我想说的是,在跨语言应用程序中,我确实喜欢静态变量。 你可以有一个类,你传递一种语言(例如 $_ SESSION [‘ language’]) ,它反过来访问其他类的设计如下:

Srings.php //The main class to access
StringsENUS.php  //English/US
StringsESAR.php  //Spanish/Argentina
//...etc

使用 Strings: : getString (“ some string”)是从应用程序中抽象语言用法的一种很好的方法。您可以按照自己的意愿来做,但是在这种情况下,让每个字符串文件具有字符串值的常量,这些常量可以被 String 类访问,这样做非常好。

在这里,我对大多数答案都有不同的解决方法,特别是在使用 PHP 时。我认为所有的类都应该是静态的,除非你有一个很好的理由。一些“为什么不”的理由是:

  • 您需要该类的多个实例
  • 您的类需要扩展
  • 代码的某些部分不能与任何其他部分共享类变量

让我举个例子。因为每个 PHP 脚本都生成 HTML 代码,所以我的框架有一个 HTML 编写器类。这确保了没有其他类会尝试编写 HTML,因为它是一个专门的任务,应该集中到一个单一的类。

通常,您会这样使用 html 类:

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();

每次调用 get _ buffer ()时,它都会重置所有内容,以便下一个使用 html 编写器的类以已知状态启动。

我的所有静态类都有一个 init ()函数,需要在首次使用该类之前调用该函数。这与其说是必要的,不如说是惯例。

在这种情况下,静态类的替代方案是混乱的。您不会希望每个需要编写一点 html 的类都必须管理 html 编写器的实例。

现在我将给出一个不使用静态类的示例。我的表单类管理一个表单元素列表,如文本输入、下拉列表等。它通常是这样使用的:

$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 中的所有内容都是静态的,除非有一个不应该静态的原因。