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


170

PHP是我的第一门编程语言。我不能完全确定何时使用静态类与实例化对象。

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

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

一个简单的例子。一个博客。博客的哪些对象最好实现为静态或实例化对象?DB班?为什么不只在全局范围内实例化db对象?为什么不将每个对象都设为静态呢?性能如何?

都是风格吗?有适当的方法来做这些东西吗?

Answers:


123

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

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

  • 使用实例化的类,其中每个对象都有自己的数据(例如用户有一个名称)
  • 当它只是可以在其他内容上使用的工具时,请使用静态类(例如,用于将BB代码转换为HTML的语法转换器;它本身没有生命)

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

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

关于静态数据的另一件事是程序中仅存在一个实例:如果您将MyClass :: $ myData设置为某个位置的某个值,则它将在所有位置都具有该值-谈到用户,您将只能拥有一个用户-那不是很好,不是吗?

对于博客系统,我能说什么?实际上,我认为我不会写成静态的。也许是数据库访问类,但最后可能不是


因此,在处理照片,用户,帖子等独特事物时,基本上使用对象,而在用于一般事物时使用静态对象呢?
罗伯特·罗查

1
说说用户,每个请求只有一个活跃用户。因此拥有静态静态请求的活跃用户就很有意义
My1

69

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

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

在其他方法中进行静态方法调用实际上比导入全局变量差。在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具有功能,我们应该使用它们。


1
@Ionut,我一点都不怀疑。我意识到位置/角色也很有价值。但是,就像XP / Agile的所有相关内容一样,它的名称实在令人发指。
杰森

2
我相信“依赖注入”这个术语听起来过于复杂。地段以及在SO和Google-land上的大量阅读内容。就“单身”而言,这确实让我想到了一些东西:slideshare.net/go_oh/…关于为什么PHP单身人士真的没有任何意义的讨论。希望这对一个古老的问题有所帮助:)
dudewad 2013年

22

因此在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类上编写了对单个帖子起作用的方法,并编写了对帖子集合起作用的静态方法。


4
这个答案非常好,尤其是您所做的更新,因为这就是我最后回答这个问题的原因。我了解“ ::”和“ $ this”的功能,但是当您考虑时,给出的示例是从关联数组中的数据库中提取博客文章的。它增加了一个全新的维度,即也是这个问题的基调。
Gerben Jacobs

这是对这一备受质疑的PHP问题的最佳实用(相对于理论)答案之一,附录非常有帮助。我认为这是许多PHP程序员正在寻求的答案,对此表示反对!
ajmedway

14

都是风格吗?

很长的路要走。您可以编写完美的面向对象程序,而无需使用静态成员。实际上,有些人会认为静态成员首先是杂质。我建议-作为oop的初学者,您应尽量避免完全使用静态成员。它将迫使您以面向对象的方式而不是过程风格地朝着写作的方向发展。


13

在这里,对于大多数答案,我有不同的方法,尤其是在使用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中的所有内容都是静态的,除非有一个原因不应这样做。


您能否编写以下内容的示例“代码的某些部分不能与任何其他部分共享类变量” #JG Estiot
khurshed alam

10

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

这些都是详尽的陈述。如果我有一组与主题相关的功能,但是实例数据是完全不合适的,那么我宁愿将它们定义在一个类中,而不是将每个定义在全局名称空间中。我只是使用PHP5中提供的机制来

  • 给他们一个命名空间-避免任何名称冲突
  • 将它们保持在实际位置上,而不是分散在整个项目中-其他开发人员可以更轻松地找到已经可用的内容,并且不太可能重新发明轮子
  • 让我使用类consts代替全局定义的任何魔术值

这完全是增强较高的内聚力和较低的耦合度的便捷方法。

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


2
“为了防止类的实例化,也可以将其声明为抽象”-我非常喜欢。我以前曾读过一些有关使__construct()成为私有函数的人的信息,但是我认为,如果有必要,我可能会使用摘要
Kavi Siegel

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'

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

我希望这会有所帮助,以及其他有助于清除问题的答案。作为附带说明,请检查“ 内聚 ”和“ 耦合 ”。它们概述了在编写适用于所有编程语言的代码时要使用的一些非常非常好的做法。


6

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

紧密耦合使您的代码可重用性降低,易碎且易于出现错误。您要避免静态类能够将类的实例传递给其他类。

是的,这只是许多其他原因之一,其中一些已经被提及。


3

通常,除非绝对必须在所有实例之间共享它,或者除非要创建单例,否则应使用成员变量和成员函数。使用成员数据和成员函数可以使您的函数重用于多个不同的数据段,而如果使用静态数据和函数,则只能拥有一个数据副本用于操作。另外,尽管不适用于PHP,但是静态函数和数据导致代码不可重入,而类数据可促进重入。


3

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

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

使用Strings :: getString(“ somestring”)是从您的应用程序中提取语言用法的好方法。您可以随意执行此操作,但是在这种情况下,让每个字符串文件都具有带有Strings类访问的字符串值的常量,效果很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.