属性和方法可见性
到目前为止,Book
类中定义的所有属性和方法都被标记为 public
。这意味着任何人都可以访问它们,或者更准确地说,从任何地方都可以访问它们。这就是所谓的属性或方法的可见性,可见性有三种类型。按照限制性从大到小的顺序,它们如下:
-
private:这种类型只允许访问同一类中的成员。如果 A 和 B 是类 C 的实例,则 A 可以访问 B 的属性和方法:
-
protected:这种类型只允许访问同一类的成员和从该类继承的类的实例。您将在下一节看到继承。
-
public:这种类型指的是可以从任何地方访问的属性或方法。任何类或类外的一般代码都可以访问它。
为了展示一些示例,让我们先在应用程序中创建第二个类。将其保存到 Customer.php
文件中:
<?php
class Customer {
private $id;
private $firstname;
private $surname;
private $email;
public function __construct(
int $id,
string $firstname,
string $surname,
string $email
) {
$this->id = $id;
$this->firstname = $firstname;
$this->surname = $surname;
$this->email = $email;
}
}
该类代表一个客户,其属性包括书店通常了解的客户的一般信息。但出于安全考虑,我们不能让所有人都知道顾客的个人资料,因此我们将每个属性都设置为 private
。
到目前为止,我们一直在同一个 Book.php
文件中添加创建对象的代码,但既然现在我们有了两个类,自然应该把类留在各自的文件中,而在另一个文件中创建和使用对象。我们把第三个文件命名为 init.php
。为了实例化某个类的对象,PHP 需要知道该类在哪里。为此,只需在文件中加入 require_once
。
<?php
require_once __DIR__ . '/Book.php';
require_once __DIR__ . '/Customer.php';
$book1 = new Book("1984", "George Orwell", 9785267006323, 12);
$book2 = new Book("To Kill a Mockingbird", "Harper Lee", 9780061120084, 2);
$customer1 = new Customer(1, 'John', 'Doe', 'johndoe@mail.com');
$customer2 = new Customer(2, 'Mary', 'Poppins', 'mp@mail.com');
您不需要每次都包含这些文件。一旦包含这些文件,PHP 就会知道在哪里可以找到这些类,即使您的代码在不同的文件中。
类约定
在使用类时,您应该知道,为了确保代码的简洁和易于维护,每个人都会尽量遵守一些约定。最重要的约定如下:
|
为了展示可见性的工作原理,让我们尝试以下代码:
$book1->available = 2; // OK
$customer1->id = 3; // Error!
我们已经知道,Book 类对象的属性是公共的,因此可以从外部编辑。但是,当试图更改 Customer
的值时,PHP 就会抱怨,因为它的属性是私有的。
封装
在处理对象时,您必须了解和应用的最重要概念之一就是封装。封装试图将对象的数据与对象的方法组合在一起,以便将对象的内部结构隐藏起来,不被外界所知。简单地说,如果对象的属性是私有的,而更新这些属性的唯一途径是通过公共方法,那么就可以说使用了封装。
使用封装的原因是为了让开发人员更容易更改类的内部结构,而不会直接影响到使用该类的外部代码。例如,假设我们的 Customer
类现在有两个定义名称的属性-- firstname
和 surname
--需要更改。从现在起,我们只有一个包含这两个属性的属性名。如果我们直接访问它的属性,就应该更改所有的访问!
相反,如果我们将属性设置为私有,并启用两个公共方法,即 getFirstname
和 getSurname
,即使我们必须更改类的内部结构,我们也可以只更改这两个方法的实现—这两个方法只在一个地方—使用我们类的其他代码将不会受到任何影响。这一概念也被称为 信息隐藏。
实现这一想法的最简单方法是将类的所有属性设置为私有,并为每个属性启用两个方法:一个将获取当前值(也称为 getter),另一个将允许您设置一个新值(称为 setter)。这至少是封装数据最常见、最简单的方法。
但是,让我们更进一步:在定义一个类时,想想你希望用户能够更改和检索的数据,然后只为它们添加设置器和获取器。例如,客户可能会更改他们的电子邮件地址,但一旦我们创建了他们,他们的名字、姓氏和 ID 就会保持不变。该类的新定义如下:
<?php
class Customer {
private $id;
private $name;
private $surname;
private $email;
public function __construct(
int $id,
string $firstname,
string $surname,
string $email
) {
$this->id = $id;
$this->firstname = $firstname;
$this->surname = $surname;
$this->email = $email;
}
public function getId(): id {
return $this->id;
}
public function getFirstname(): string {
return $this->firstname;
}
public function getSurname(): string {
return $this->surname;
}
public function getEmail(): string {
return $this->email;
}
public function setEmail(string $email) {
$this->email = $email;
}
}
另一方面,我们的 books 也几乎保持不变。唯一可能的变化是可用单位的数量。但我们通常一次只取或添加一本书,而不是设置具体的可用单位数,所以这里的设置器并不是很有用。我们已经有了 getCopy
方法,它可以在可能的情况下获取一份副本;让我们添加一个 addCopy
方法,再加上其余的获取器:
<?php
class Book {
private $isbn;
private $title;
private $author;
private $available;
public function __construct(
int $isbn,
string $title,
string $author,
int $available = 0
) {
$this->isbn = $isbn;
$this->title = $title;
$this->author = $author;
$this->available = $available;
}
public function getIsbn(): int {
return $this->isbn;
}
public function getTitle(): string {
return $this->title;
}
public function getAuthor(): string {
return $this->author;
}
public function isAvailable(): bool {
return $this->available;
}
public function getPrintableTitle(): string {
$result = '<i>' . $this->title . '</i> - ' . $this->author;
if (!$this->available) {
$result .= ' <b>Not available</b>';
}
return $result;
}
public function getCopy(): bool {
if ($this->available < 1) {
return false;
} else {
$this->available--;
return true;
}
}
public function addCopy() {
$this->available++;
}
}
当应用程序中类的数量以及类之间关系的数量增加时,用图表来表示这些类是很有帮助的。我们称这种图为 UML 类图,或者层次树。我们这两个类的层次树如下:

我们只展示公共方法,因为受保护或私有的方法不能从类外部调用,因此,对于只想在外部使用这些类的开发人员来说,这些方法并无用处。