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

Posted

技术标签:

【中文标题】何时使用静态类与实例化类【英文标题】:When to use static vs instantiated classes 【发布时间】:2010-11-14 05:03:13 【问题描述】:

php 是我的第一门编程语言。我不知道何时使用静态类和实例化对象。

我意识到您可以复制和克隆对象。然而,在我使用 php 的所有时间里,任何对象或函数总是以单个返回(数组、字符串、int)值或 void 结束。

我了解电子游戏角色课程等书籍中的概念。 复制汽车对象并将新的汽车对象设为红色,这一切都说得通,但它在 php 和 web 应用程序中的应用不合理。

一个简单的例子。一个博客。博客的哪些对象最好实现为静态或实例化对象? DB类?为什么不在全局范围内实例化 db 对象呢?为什么不让每个对象都变成静态的呢?性能怎么样?

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

【问题讨论】:

【参考方案1】:

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

考虑事物的最简单方法可能是:

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

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

关于静态方法/类的一件事是它们不利于单元测试(至少在 PHP 中,但也可能在其他语言中)。

关于静态数据的另一件事是它的一个实例只存在于您的程序中:如果您在某处将 MyClass::$myData 设置为某个值,它就会有这个值,而且只有它,无处不在——说到用户,您将只能拥有一个用户 - 这不是很好,是吗?

对于博客系统,我能说什么?实际上,我认为我不会写太多静态的东西;也许是 DB-access 类,但最终可能不是 ^^

【讨论】:

所以基本上在处理照片、用户、帖子等独特事物时使用对象,而在处理一般事物时使用静态对象? 说到用户,每个请求只有一个活跃用户。所以让请求静态的活跃用户有意义【参考方案2】:

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

使用静态方法的代码很难测试 使用静态方法的代码很难扩展

在其他方法中调用静态方法实际上比导入全局变量更糟糕。在 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有函数,我们应该使用它们。

【讨论】:

@Ionut,我一点也不怀疑。我意识到职位/角色也很有价值;然而,就像所有与 XP/Agile 相关的东西一样,它有一个非常笨拙的名字。 “依赖注入”我认为这个术语听起来过于复杂。在 SO 和 google-land 上进行了大量阅读。就“单例”而言,这确实让我想到:slideshare.net/go_oh/… 讨论为什么 PHP 单例真的没有任何意义。希望这对一个老问题有所帮助:)【参考方案3】:

所以在 PHP 中静态可以应用于函数或变量。非静态变量与类的特定实例相关联。非静态方法作用于类的实例。所以让我们组成一个名为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 类上编写作用于单个帖子的方法,并编写作用于帖子集合的静态方法。

【讨论】:

这个答案非常好,特别是你所做的更新,因为这就是我最终提出这个问题的原因。我了解“::”与“$this”的功能,但是当您考虑在内时,您给出的示例是从关联数组中的数据库中提取的博文。它增加了一个全新的维度,即这个问题的基调也是如此。 这是对这个备受追捧的 PHP 问题的最佳实用(相对于理论)答案之一,附录非常有帮助。我想这是很多 PHP 程序员都在寻求的答案,点赞!【参考方案4】:

一切都只是风格吗?

是的,还有很长的路要走。您可以编写非常好的面向对象程序,而无需使用静态成员。事实上,有些人会争辩说,静态成员首先是一个杂质。我建议 - 作为 oop 的初学者 - 你尽量避免一起使用静态成员。它会迫使你以面向对象而不是过程风格写作。

【讨论】:

【参考方案5】:

我对这里的大多数答案都有不同的方法,尤其是在使用 PHP 时。我认为所有类都应该是静态的,除非你有充分的理由不这样做。一些“为什么不”的原因是:

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

让我举一个例子。由于每个 PHP 脚本都会生成 HTML 代码,因此我的框架有一个 html writer 类。这确保没有其他类会尝试编写 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 中一切都是静态的,除非有一个不应该的原因。

【讨论】:

你能写一个下面的例子“你的代码的一部分不能与任何其他部分共享类变量”#JG Estiot【参考方案6】:

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

这些都是相当笼统的陈述。如果我有一组主题相关的函数,但实例数据完全不合适,我宁愿将它们定义在一个类中,而不是将它们中的每一个都定义在全局命名空间中。我只是使用 PHP5 中可用的机制来

给他们一个命名空间——避免任何名称冲突 将它们物理上放在一起,而不是分散在一个项目中 - 其他开发人员可以更轻松地找到现有的东西,并且不太可能重新发明*** 让我使用类 consts 而不是全局定义来定义任何魔法值

这完全是一种实现更高内聚和更低耦合的便捷方式。

而且 FWIW —— 至少在 PHP5 中没有“静态类”这样的东西;方法和属性可以是静态的。为了防止类的实例化,也可以将其声明为抽象的。

【讨论】:

“为了防止类的实例化,也可以将其声明为抽象的”——我非常喜欢这一点。我之前读过有人将 __construct() 设为私有函数,但我认为如果需要,我可能会使用 abstract【参考方案7】:

首先问问自己,这个对象将代表什么?对象实例适用于对不同的动态数据集进行操作。

一个很好的例子是 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'

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

我希望这会有所帮助,以及其他有助于解决问题的答案。作为旁注,请查看“cohesion”和“coupling”。它们概述了在编写适用于所有编程语言的代码时使用的一些非常非常好的做法。

【讨论】:

【参考方案8】:

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

紧密耦合会降低您的代码的可重用性、脆弱性并且容易出现错误。您希望避免静态类能够将类的实例传递给其他类。

是的,这只是已经提到的许多其他原因之一。

【讨论】:

【参考方案9】:

一般来说,你应该使用成员变量和成员函数,除非它绝对必须在所有实例之间共享,或者除非你正在创建一个单例。使用成员数据和成员函数可以让您将函数重用于多条不同的数据,而如果您使用静态数据和函数,则您只能拥有一份您操作的数据副本。此外,虽然不适用于 PHP,但静态函数和数据会导致代码不可重入,而类数据有助于重入。

【讨论】:

【参考方案10】:

我想说,在跨语言应用程序中,肯定有我想要静态变量的情况。 您可以拥有一个将语言传递给的类(例如 $_SESSION['language']),然后它会访问其他设计如下的类:

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

使用 Strings::getString("somestring") 是从应用程序中抽象出语言使用的好方法。你可以随心所欲地做,但在这种情况下,让每个字符串文件都有带有字符串值的常量,这些字符串值可以被 Strings 类访问。

【讨论】:

以上是关于何时使用静态类与实例化类的主要内容,如果未能解决你的问题,请参考以下文章

深入理解C# 静态类与非静态类静态成员的区别

java中静态成员变量、实例变量、局部变量何时创建、何时销毁?

静态类与非静态类静态成员的区别

OO 普通类与静态类的区别

C函数中如何调用未实例化类的成员函数

Python高级语法-对象实例对象属性-类与实例,class方法静态方法等(4.6.1)