何时使用静态vs实例化类

use*_*119 168 php oop class

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

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

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

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

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

Pas*_*TIN 122

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

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

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

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

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

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

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


Ion*_*tan 69

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

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

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

作为谷歌敏捷教练的MiškoHevery有一个有趣的理论,或者更确切地说,我们应该将对象创建时间与使用对象的时间分开.因此,程序的生命周期分为两部分.第一部分(main()方法就是说),它负责应用程序中的所有对象连接以及执行实际工作的部分.

所以不要:

class HttpClient
{
    public function request()
    {
        return HttpResponse::build();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们应该做:

class HttpClient
{
    private $httpResponseFactory;

    public function __construct($httpResponseFactory)
    {
        $this->httpResponseFactory = $httpResponseFactory;
    }

    public function request()
    {
        return $this->httpResponseFactory->build();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在索引/主页面中,我们会这样做(这是对象布线步骤,或者是创建程序使用的实例图的时间):

$httpResponseFactory = new HttpResponseFactory;
$httpClient          = new HttpClient($httpResponseFactory);
$httpResponse        = $httpClient->request();
Run Code Online (Sandbox Code Playgroud)

主要思想是将依赖关系从类中分离出来.这样代码就更具可扩展性,对我来说最重要的部分是可测试的.为什么测试更重要?因为我并不总是编写库代码,所以可扩展性并不重要,但是当我进行重构时,可测试性很重要.无论如何,可测试的代码通常会产生可扩展的代码,因此它实际上并不是一种情况.

MiškoHevery还明确区分单身人士和单身人士(有或没有资本S).区别很简单.具有小写"s"的单例由索引/ main中的布线强制执行.您实例化一个没有实现Singleton模式的类的对象,并注意您只将该实例传递给任何其他需要它的实例.另一方面,具有大写"S"的Singleton是经典(反)模式的实现.基本上是伪装的全球性,在PHP世界中没有多大用处.到目前为止我还没有见过一个.如果您想要所有类使用单个数据库连接,最好这样做:

$db = new DbConnection;

$users    = new UserCollection($db);
$posts    = new PostCollection($db);
$comments = new CommentsCollection($db);
Run Code Online (Sandbox Code Playgroud)

通过上面的操作,很明显我们有一个单例,我们也有一个很好的方法在我们的测试中注入一个模拟或存根.令人惊讶的是,单元测试如何导致更好的设计.但是当你认为测试迫使你思考你使用该代码的方式时,它会很有意义.

/**
 * 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用的唯一情况(我已经用它来模仿PHP 5.3中的JavaScript原型对象)静态成员是我知道相应字段将具有相同的跨实例值.此时,您可以使用静态属性,也可以使用一对静态getter/setter方法.无论如何,不​​要忘记添加用实例成员覆盖静态成员的可能性.例如,Zend Framework使用静态属性来指定实例中使用的DB适配器类的名称Zend_Db_Table.自从我使用它们已经有一段时间了,所以它可能不再具有相关性,但这就是我记忆中的方式.

不处理静态属性的静态方法应该是函数.PHP有功能,我们应该使用它们.

  • "依赖注入"我认为这是一个过于复杂的声音术语.在SO和google-land上有很多阅读和阅读.就"单身人士"而言,这里真的让我想到了:http://www.slideshare.net/go_oh/singletons-in-php-why-they-are-bad-and-how-you-can - 从他们的应用程序中消除它们谈论为什么PHP单例实际上没有任何意义.希望这对一个老问题有点贡献:) (2认同)

Raf*_*afe 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

另一方面,使用静态函数,您可以编写如下类:

class blog_post_helper {
    public static function find_related($blog_post) {
         // Do stuff.
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,由于该函数是静态的,并且不对任何特定的博客文章起作用,因此您必须将博客文章作为参数传递.

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


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

在OO场景中,您在BlogPost类上编写了针对各个帖子的方法,并编写了对帖子集合起作用的静态方法.

  • 这个答案非常好,特别是你所做的更新,因为这就是我最终提出这个问题的原因.我理解"::"vs"$ this"的功能,但是当你添加到帐户时,你给出的博客文章的例子是从一个关联数组中的数据库中提取出来的.它增加了一个全新的维度,即也是这个问题的基础语气. (4认同)

tro*_*skn 14

这一切都只是风格吗?

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


JG *_*iot 12

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

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

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

通常,您将使用这样的html类:

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();
Run Code Online (Sandbox Code Playgroud)

每次调用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
Run Code Online (Sandbox Code Playgroud)

你无法用静态类做到这一点,特别是考虑到每个添加的类的一些构造函数做了很多工作.此外,所有元素的继承链都非常复杂.所以这是一个明确的例子,不应该使用静态类.

大多数实用程序类(如转换/格式化字符串)都是静态类的良好候选者.我的规则很简单:PHP中的一切都是静态的,除非有一个原因不应该.


gra*_*rks 10

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

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

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

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

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

  • "为了防止类的实例化,人们可以宣布它是抽象的,也是" - 我非常喜欢它.我之前读过关于让__construct()成为私有函数的人,但我认为如果我需要,我可能会使用抽象 (2认同)

Tre*_*res 7

首先问问自己,这个对象代表什么?对象实例适用于在单独的动态数据集上操作.

一个很好的例子是ORM或数据库抽象层.您可能有多个数据库连接.

$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));
Run Code Online (Sandbox Code Playgroud)

这两个连接现在可以独立运行:

$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);
Run Code Online (Sandbox Code Playgroud)

现在在这个包/库中,可能有其他类,如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();
}
Run Code Online (Sandbox Code Playgroud)

静态类的一个很好的例子是处理注册表变量或会话变量的东西,因为每个用户只有一个注册表或一个会话.

在您的申请的一部分:

Session::set('someVar', 'toThisValue');
Run Code Online (Sandbox Code Playgroud)

在另一部分:

Session::get('someVar'); // returns 'toThisValue'
Run Code Online (Sandbox Code Playgroud)

由于每个会话一次只有一个用户,因此为Session创建实例没有意义.

我希望这会有所帮助,以及帮助澄清事情的其他答案.作为旁注,请查看" 内聚 "和" 耦合 ".他们概述了在编写适用于所有编程语言的代码时要使用的一些非常好的实践.


Muh*_*han 6

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

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

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