PHP构造函数的目的

56

我正在使用类和对象类结构进行编程,但不是在复杂的层面上——只是关于类和函数,然后在一个地方进行实例化。

至于__construct__destruct,请简单告诉我:构造函数和析构函数的目的是什么?

我知道学校级别的理论解释,但我期望得到一些真实世界中的例子,例如我们何时需要使用它们。

请提供一个例子。

谢谢


每一个例子都非常好,现在我有点明白为什么人们会寻找构造函数和析构函数了。但是告诉我,如果我不使用构造函数,我会失去什么…因为在程序中我们使用了很多函数和变量,那么对于函数和变量,我该如何使用构造函数呢?是否有动态分配的方法,也就是说,我们应该在构造函数内调用所有函数名称? - Bharanikumar
1
不接受……答案的可能原因是什么?我发现他们的答案真正回答了问题。 - NullPoiиteя
6个回答

52

构造函数是在对象已被初始化(分配内存,复制实例属性等)之后执行的函数。它的目的是将对象置于有效状态。

通常情况下,为了使一个对象处于可用状态,需要一些数据。构造函数的目的是强制在实例化时将这些数据提供给对象,并禁止没有这些数据的实例。

考虑一个简单的类,它封装了一个字符串,并有一个返回该字符串长度的方法。可能的实现方式之一如下:

class StringWrapper {
    private $str;

    public function setInnerString($str) {
        $this->str = (string) $str;
    }

    public function getLength() {
        if ($this->str === null)
            throw new RuntimeException("Invalid state.");
        return strlen($this->str);
    }
}

为了保证函数处于有效状态,必须在调用getLength之前先调用setInnerString。通过使用构造函数,可以强制所有实例在调用getLength时都处于良好状态:

class StringWrapper {
    private $str;

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

    public function getLength() {
        return strlen($this->str);
    }
}

你也可以保留 setInnerString 方法,允许在实例化后更改字符串。

当一个对象即将从内存中释放时,会调用析构函数。通常,它包含清理代码(例如关闭对象持有的文件描述符)。在 PHP 中它们很少见,因为当脚本执行结束时,PHP 会清理脚本所持有的所有资源。


2
“将对象置于有效状态”的另一种说法是“确保对象的不变量为真”。类的不变量是关于实例始终应该为真的事实,例如内部 str 值应始终初始化。 - Oddthinking

42

通过例子学习:

class Person {
  public $name;
  public $surname;
  public function __construct($name,$surname){
    $this->name=$name;
    $this->surname=$surname;
  }
}

这个有什么好处呢?因为不用:

$person = new Person();
$person->name='Christian';
$person->surname='Sciberras';

你可以使用:

$person = new Person('Christian','Sciberras');

哪种方法代码更少且看起来更整洁!

注意:如下回复所述,构造函数/析构函数用于各种用途,包括:变量的初始化/去初始化(特别是值是变量时),内存分配/释放,不变式(可能被超越)以及更清晰的代码。 我还想指出,“更清晰的代码”不仅仅是“甜头”,而且提高了可读性、可维护性等。


除了公共属性的可疑用法外,我认为这个答案没有抓住重点。构造函数不仅仅是语法糖,它们是强制不变量的一种方式。 - Artefacto
6
它不仅看起来更整洁,还能预防错误:如果你忘记设置姓氏会发生什么?构造函数将强制你这样做。 - johannes
1
OP要求提供一个实际例子,我给了一个。使用公共属性是为了让示例在两种情况下都可以正常工作。 我的回答没有漏掉任何重点。构造函数可用于任何事情(包括您提到的内容),以设置默认变量。 快速示例:class A { protected $path; public function __construct(){ $this->path=getcwd().'path'; } } - Christian

8
构造函数在实例化类的时候运行。所以如果你有一个名为Person的类:
class Person {

    public $name = 'Bob'; // this is initialization
    public $age;

    public function __construct($name = '') {
        if (!empty($name)) {
            $this->name = $name;
        }
    }

    public function introduce() {
        echo "I'm {$this->name} and I'm {$this->age} years old\n";
    }

    public function __destruct() {
        echo "Bye for now\n";
    }
}

展示如下:
$person = new Person;
$person->age = 20;
$person->introduce();

// I'm Bob and I'm 20 years old
// Bye for now

我们可以通过构造函数参数来覆盖初始化设置的默认值:
$person = new Person('Fred');
$person->age = 20;
$person->introduce();

// if there are no other references to $person and 
// unset($person) is called, the script ends 
// or exit() is called __destruct() runs
unset($person);

// I'm Fred and I'm 20 years old
// Bye for now

希望这有助于展示构造函数和析构函数的调用位置,它们有什么用处?
  1. __construct() 可以使用资源或更复杂的数据结构默认类成员。
  2. __destruct() 可以释放文件和数据库句柄等资源。
  3. 构造函数常用于类组合构造函数注入所需依赖项

7
一个类的构造函数定义了当您从该类实例化对象时会发生什么。类的析构函数定义了销毁对象实例时会发生什么。
请参阅PHP手册中的构造函数和析构函数
PHP 5允许开发人员为类声明构造方法。具有构造方法的类在每个新创建的对象上调用此方法,因此它适用于对象在使用之前需要的任何初始化。
并且
PHP 5引入了与其他面向对象语言(如C ++)类似的析构概念。当所有对特定对象的引用被删除或显式销毁对象或按关闭顺序的任何顺序时,将调用析构方法。
实践中,您使用构造函数将对象放入最小有效状态。这意味着您将传递给构造函数的参数分配给对象属性。如果您的对象使用某些无法直接分配为属性的数据类型,则在此处创建它们,例如:
class Example
{
    private $database;
    private $storage;

    public function __construct($database)
    {
        $this->database = $database;
        $this->storage = new SplObjectStorage;
    }
}

请注意,为了使您的对象可测试,构造函数不应该执行任何真正的工作:
在构造函数中进行的工作,如:创建/初始化协作者,与其他服务通信以及设置自身状态的逻辑,会移除测试所需的接缝,迫使子类/模拟继承不需要的行为。过多的构造函数工作会阻止实例化或更改测试中的协作者。
在上述示例中,$database是一个协作者。它具有自己的生命周期和目的,并且可能是共享实例。您不会在构造函数中创建它。另一方面,SplObjectStorageExample的一个重要部分。它具有完全相同的生命周期,并且不与其他对象共享。因此,在构造函数中new它是可以的。
同样地,您可以使用析构函数来清理对象后的工作。在大多数情况下,这是不必要的,因为 PHP 会自动处理它。这就是为什么您在野外看到的构造函数比析构函数多得多的原因。

好的,抱歉。以缓存为例,它的基本目的是提高网站性能。即使我可以监控这个缓存概念,我们也可以实时展示,比如在执行查询时,在一个文件中复制记录,下一次用户尝试获取记录时,系统只会查找文件而不是实时数据库。是否有任何类似的例子可以给出? - Bharanikumar
@Bharanikumar,这是一个完全不同于你上面提出的问题。如果你需要缓存,请查看APC或memcache并学习一些代码示例,例如Zend_Cache。 - Gordon
其实我知道缓存。不过为了更容易理解,能否有人举个例子,像缓存一样的东西?这就是我问的问题,但现在我已经得到了很多例子。 - Bharanikumar

0

我发现当我在考虑构造函数之前想到new关键字时,最容易理解:它仅告诉我的变量将被给予其数据类型的新对象,根据我调用的构造函数和传递给它的参数,我可以定义到达时对象的状态。

没有新对象,我们将生活在null的世界中,并遭遇崩溃!

从C++的角度来看,析构函数是最明显的:如果您没有一个析构方法来删除所有指向的内存,那么在程序退出后它将继续使用,导致客户端操作系统出现泄漏和延迟,直到下一次重新启动。

我确定这里有足够多的信息,但另一个角度总是有帮助的,这是我注意到的!


关于内存泄漏的部分,它不是很准确。当您退出程序时,任何泄漏都会被清除(除非内存驻留在另一个程序中)。内存泄漏主要在程序使用期间产生影响,而不是在程序退出时。例如,我曾经有过一个程序,在1小时的使用中泄漏了700MB。但是当关闭程序后,一切都恢复正常。 - Christian

0

构造函数是类的一种函数,当创建类的对象时自动执行,我们不需要单独调用该构造函数。我们可以将构造函数称为魔术方法,因为在 PHP 中,魔术方法以双下划线字符开头。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接