何时使用静态vs实例化的类

PHP是我的第一个编程语言。 当我使用静态类和实例化对象的时候,我不能完全包裹我的头。

我意识到你可以复制和克隆对象。 然而,在我所有的时间使用PHP的任何对象或函数总是作为单个返回(数组,string,整数)值或无效。

我理解像电子游戏angular色类的书籍中的概念。 重复的汽车对象,使新的红色 ,这一切都有道理,但什么不是它的应用程序在PHP和Web应用程序。

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

这只是风格吗? 有没有一个正确的方法来做这个东西?

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

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

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

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

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

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

对于博客系统,我能说什么? 实际上,我觉得没有太多的东西会写成静态的。 也许DB访问类,但可能不是,最后^^

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

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

在其他方法中调用静态方法实际上比导入全局variables更差。 在PHP中,类是全局符号,所以每次调用静态方法时都依赖全局符号(类名)。 这是全球性的邪恶的情况。 我用Zend Framework的某些组件遇到了这种方法的问题。 有些类使用静态方法调用(工厂)来build立对象。 为了得到返回的自定义对象,我不可能为该实例提供另一个工厂。 解决这个问题的方法是只使用实例和instace方法,并在程序开始时强制执行singleton等。

在Google工作的敏捷教练MiškoHevery有一个有趣的理论,或者更确切的说,我们应该把对象创build的时间和使用对象的时间分开。 所以一个程序的生命周期被分成两部分。 第一部分( 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(); } } 

然后,在索引/主页面,我们会这样做(这是对象连线步骤,或创build实例的graphics以供程序使用):

 $httpResponseFactory = new HttpResponseFactory; $httpClient = new HttpClient($httpResponseFactory); $httpResponse = $httpClient->request(); 

主要想法是将你的类的依赖分离出来。 这样代码就更具可扩展性,对我来说最重要的部分是可testing的。 为什么testing更重要? 因为我并不总是编写库代码,所以可扩展性并不重要,但是当我重构时,可testing性是非常重要的。 无论如何,可testing的代码通常会产生可扩展的代码,所以它不是一个真正的二者或情况。

MiškoHevery也明确区分了单身人士和单身人士(有或没有大写字母S)。 区别非常简单。 带有小写字母“s”的单身人士由index / main中的接线强制执行。 你实例化一个没有实现Singleton模式的类的对象,注意只把这个实例传递给需要它的任何其他实例。 另一方面,具有大写字母“S”的辛格尔顿则是古典(反)模式的实现。 基本上是一个全球性的伪装,在PHP世界中没有太多的用处。 到目前为止我还没有看到一个。 如果你想要一个数据库连接被所有的类使用,最好是这样做:

 $db = new DbConnection; $users = new UserCollection($db); $posts = new PostCollection($db); $comments = new CommentsCollection($db); 

通过这样做很明显我们有一个单例,我们也有一个很好的方法来在我们的testing中注入一个模拟或存根。 令人惊讶的是unit testing如何导致更好的devise。 但是,当您认为testing迫使您考虑使用该代码的方式时,这是非常有意义的。

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

所以在PHP中,静态可以应用于函数或variables。 非静态variables绑定到一个类的特定实例。 非静态方法作用于类的一个实例。 所以我们来组build一个名为BlogPost的类。

title将是一个非静态成员。 它包含该博客文章的标题。 我们也可能有一个名为find_related()的方法。 这不是静态的,因为它需要来自博客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. } } 

在这种情况下,由于该函数是静态的,并且不在任何特定博客文章上作用,所以您必须将博文作为parameter passing。

从根本上讲,这是一个关于面向对象devise的问题。 你的课是你系统中的名词,而作用于它们的function是动词。 静态函数是程序性的。 你传递函数的对象作为参数。


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

在OO场景中,您可以在BlogPost类上编写对各个post起作用的方法,然后编写对post集合起作用的静态方法。

这只是风格吗?

很长一段时间,是的。 您可以编写完美的面向对象的程序,而无需使用静态成员。 事实上,有些人会认为,静态成员首先是一个不纯洁的东西。 我会build议 – 作为一个初学者,你尽量避免静态成员在一起。 它会迫使你在面向对象而不是程序风格的写作方向上。

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

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

  • 给他们所有的名字空间 – 避免任何名称冲突
  • 使它们保持在一起,而不是分散在一个项目中 – 其他开发人员可以更容易地find已有的东西,并且不太可能重新发明轮子
  • 让我使用类const,而不是全局定义任何魔术值

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

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

在这里我有一个不同的方法来解决大多数问题,特别是在使用PHP时。 我认为,除非你有充分的理由,否则所有的类都应该是静态的。 一些“为什么不”的原因是:

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

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

通常情况下,你可以像这样使用html类:

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

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

我所有的静态类都有一个init()函数,在第一次使用类之前需要调用它。 这是比惯例更重要的。

在这种情况下静态类的替代是凌乱的。 你不希望每一个需要编写一点html的类都必须pipe理一个html编写器的实例。

现在我将给你一个什么时候不使用静态类的例子。 我的表单类pipe理一个表单元素,如文本input,下拉列表等列表。 它通常是这样使用的:

 $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 

你无法用静态类来做到这一点,特别是考虑到每个添加的类的一些构造函数做了很多工作。 而且,所有元素的inheritance链相当复杂。 所以这是一个明确的例子,静态类不应该被使用。

大多数实用程序类(如转换/格式化string)是静态类的良好select。 我的规则很简单:除非有一个原因,否则PHP中的一切都是静态的。

首先问自己,这个对象将代表什么? 对象实例适合在单独的dynamic数据集上运行。

一个好的例子是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(); } 

静态类的一个很好的例子是处理registryvariables或会话variables,因为每个用户只有一个registry或一个会话。

在您的应用程序的一部分:

 Session::set('someVar', 'toThisValue'); 

另一部分:

 Session::get('someVar'); // returns 'toThisValue' 

由于每个会话一次只能有一个用户,因此为Session创build一个实例是没有意义的。

我希望这有助于,以及其他答案,以帮助清除事情。 作为一个方面说明,检查“ 凝聚力 ”和“ 耦合 ”。 他们概述了在编写适用于所有编程语言的代码时使用的一些非常好的实践。

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

紧密的耦合使得你的代码更less的可重用,脆弱和容易出错。 你想避免静态类能够将类的实例传递给其他类。

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

一般来说,你应该使用成员variables和成员函数,除非它绝对必须在所有实例之间共享,或者除非你正在创build一个单例。 使用成员数据和成员函数可以让您重用多个不同的数据,但是如果您使用静态数据和函数,则只能有一个数据副本。 另外,尽pipe不适用于PHP,静态函数和数据导致代码不可重入,而类数据有助于重入。

我想说的是,在跨语言应用程序中,肯定会遇到静态variables的情况。 你可以有一个你传递语言的类(例如$ _SESSION ['language']),然后访问其他devise如下的类:

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

使用string:: getString(“somestring”)是从您的应用程序中抽象出您的语言使用情况的好方法。 你可以做到这一点,但是在这种情况下,每个string文件都具有string值的常量,这些string值可以被Strings类访问。