属性和方法可见性

到目前为止,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 就会知道在哪里可以找到这些类,即使您的代码在不同的文件中。

类约定

在使用类时,您应该知道,为了确保代码的简洁和易于维护,每个人都会尽量遵守一些约定。最重要的约定如下:

  • 每个类都应放在一个文件中,文件名应与类名相同,并带有 .php 扩展名

  • 类名应使用 CamelCase,即每个单词应以大写字母开头,其余单词以小写字母结尾

  • 一个文件应只包含一个类的代码

  • 在类中,应首先放置属性,然后是构造函数,最后是其余方法

为了展示可见性的工作原理,让我们尝试以下代码:

$book1->available = 2; // OK
$customer1->id = 3; // Error!

我们已经知道,Book 类对象的属性是公共的,因此可以从外部编辑。但是,当试图更改 Customer 的值时,PHP 就会抱怨,因为它的属性是私有的。

封装

在处理对象时,您必须了解和应用的最重要概念之一就是封装。封装试图将对象的数据与对象的方法组合在一起,以便将对象的内部结构隐藏起来,不被外界所知。简单地说,如果对象的属性是私有的,而更新这些属性的唯一途径是通过公共方法,那么就可以说使用了封装。

使用封装的原因是为了让开发人员更容易更改类的内部结构,而不会直接影响到使用该类的外部代码。例如,假设我们的 Customer 类现在有两个定义名称的属性-- firstnamesurname --需要更改。从现在起,我们只有一个包含这两个属性的属性名。如果我们直接访问它的属性,就应该更改所有的访问!

相反,如果我们将属性设置为私有,并启用两个公共方法,即 getFirstnamegetSurname,即使我们必须更改类的内部结构,我们也可以只更改这两个方法的实现—​这两个方法只在一个地方—​使用我们类的其他代码将不会受到任何影响。这一概念也被称为 信息隐藏

实现这一想法的最简单方法是将类的所有属性设置为私有,并为每个属性启用两个方法:一个将获取当前值(也称为 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 类图,或者层次树。我们这两个类的层次树如下:

image 2023 11 02 10 54 58 867

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