何时使用静态vs实例化类

时间:2009-07-26 21:30:22

标签: php oop class

PHP是我的第一个编程语言。在使用静态类和实例化对象时,我无法完全理解。

我意识到你可以复制和克隆对象。但是在我使用php的所有时间中,任何对象或函数总是最终作为单个返回(数组,字符串,整数)值或无效。

我理解书中的概念,比如电子游戏角色类。 复制汽车对象,并使新的一个红色,这一切都有意义,但它在php和web应用程序中的应用是什么。

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

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

10 个答案:

答案 0 :(得分:120)

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

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

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

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

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

关于静态数据的另一个问题是程序中只存在一个实例:如果你将MyClass :: $ myData设置为某个值,它将具有此值,并且只有它,每个地方 - 说到用户,你将只能拥有一个用户 - 这不是很好,是吗?

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

答案 1 :(得分:67)

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

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

在其他方法中调用静态方法实际上比导入全局变量更糟糕。在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škoHevery还明确区分单身人士和单身人士(有或没有资本S)。区别很简单。具有小写“s”的单例由索引/ main中的布线强制执行。您实例化一个类的对象,该类的对象实现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有功能,我们应该使用它们。

答案 2 :(得分:22)

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

答案 3 :(得分:14)

  

这只是风格吗?

很长一段路,是的。您可以编写完美的面向对象的程序,而无需使用静态成员。事实上,有些人会认为静态成员首先是一种杂质。我建议 - 作为oop的初学者 - 你试图避免静态成员在一起。它将迫使您朝着面向对象而不是过程样式进行编写。

答案 4 :(得分:12)

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

  • 您需要多个班级实例
  • 您的课程需要延期
  • 您的代码的某些部分无法与其他任何部分共享类变量

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

通常,您可以像这样使用html类:

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

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

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

答案 5 :(得分:10)

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

这些都是非常彻底的陈述。如果我有一组与主题相关的函数,但实例数据完全不合适,我宁愿在类中定义它们而不是在全局命名空间中定义它们。我只是使用PHP5中提供的机制

  • 为他们提供所有命名空间 - 避免任何名称冲突
  • 将它们放在一起,而不是分散在一个项目中 - 其他开发人员可以更容易地找到已经可用的内容,并且不太可能重新发明轮子
  • 让我使用类consts而不是任何魔术值的全局定义

它只是一种方便的方法来强化更高的凝聚力和更低的耦合。

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

答案 6 :(得分: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”。他们概述了在编写适用于所有编程语言的代码时要使用的一些非常好的实践。

答案 7 :(得分:6)

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

紧密耦合使您的代码不再可重用,易碎且容易出错。您希望避免静态类将类的实例传递给其他类。

是的,这只是其中一些原因中的一个已经提到过。

答案 8 :(得分:3)

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

答案 9 :(得分:3)

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

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

使用Strings :: getString(“somestring”)是一种从应用程序中抽象语言使用的好方法。你可以随意做,但在这种情况下,每个字符串文件都有一些常量,字符串值可以通过Strings类访问。