OOP 中的封装

封装的概念是将数据和访问及更改这些数据的方法封装在一个像胶囊一样的单元中。在 PHP 中,这个 "胶囊" 就是对象或类。"胶囊" 或对象将有能力使用可见性概念来保证其数据不被读取或操作。

PHP 中的可见性

PHP 中,为了控制开发人员可以访问或使用对象中的哪些数据或函数,可以在函数声明中的函数关键字前或属性名声明前加上 publicprotectedprivate 关键字:

  • private – 只有对象内的代码可以访问该函数或属性

  • protected – 任何扩展该类的对象都将被允许访问该函数或属性

  • public – 任何对象用户都可以访问该属性或方法

那么我们开发者能从中得到什么好处呢?我们稍后会了解这一点。

让我们修改前面示例中的 Dog.php 类,如下所示:

<?php

namespace Animal;

class Dog
{
    private string $sound;
    private string $color;

    public function __construct()
    {
        $this->setSound("Bark");
        $this->setColor("Black");
    }

    public function makeSound(): string
    {
        $prefix = "Hello ";
        $suffix = " World";
        return $prefix . $this->getSound() . $suffix;
    }

    /**
    * @return string
    */
    private function getSound(): string
    {
        return $this->sound;
    }

    /**
    * @param string $sound
    */
    public function setSound(string $sound): void
    {
        $this->sound = $sound . ", my color is: " .
        $this->getColor();
    }

    /**
    * @return string
    */
    protected function getColor(): string
    {
        return $this->color;
    }

    /**
    * @param string $color
    */
    protected function setColor(string $color): void
    {
        $this->color = $color;
    }
}

创建一个 Cavoodle.php 类:

<?php

namespace Animal\Dogs;

use Animal\Dog;

class Cavoodle extends Dog
{
    public function __construct()
    {
        parent::__construct();
        // Using the protected method from the Dog class.
        $this->setColor("Chocolate");
    }
}

像这样修改 Consumer.php 类:

<?php

namespace Animal;

use Animal\Dogs\Cavoodle;

class Consumer
{
    public function sayHello()
    {
        $dog = new Dog();
        $dog->setSound("Wooooof!");
        // Will output Hello Wooooof!, my color is: Black
        $dog->makeSound();
    }
    public function sayHelloCavoodle()
    {
        $cavoodle = new Cavoodle();
        $cavoodle->setSound("Bark Bark!");
        // Will output Hello Bark Bark!!, my color is: Chocolate
        $cavoodle->makeSound();
    }
}

在此 Dog.php 示例类中,我们声明了以下内容:

  • Private:

    • $sound

    • $color

  • Protected:

    • getColor()

    • setColor()

  • Public:

    • makeSound()

    • setSound($sound)

这样,我们就保护了 Dog 对象的 $sound$color 属性值,使其不会被对象消费者直接修改。只有 Dog 对象可以直接修改这些值。对象消费者可以使用 $dog->setSound($sound) 方法修改存储在 $sound 属性中的值,但无论对象消费者在 $dog->setSound($sound) 方法中设置了什么,存储在 Dog 对象 $sound 属性中的数据都将以 $color 属性的值为后缀。对象消费者无法改变这一点;只有对象本身可以改变自己的属性值。

以下是 Consumer.php 类的屏幕截图,当我修改它时,我的 PHPStorm IDE 会自动建议 Cavoodle 对象的可用方法:

image 2023 10 23 13 29 23 686
Figure 1. Figure 4.5 – Public functions available for Dog

你会注意到,在 Consumer 类中,我们只有两个可用的函数。setSound()makeSound() 函数是我们声明为公开可见的函数。我们已经成功地限制或保护了 Cavoodle(它是 Dog 类的实例)对象的其他功能和属性。

下面的屏幕截图显示,当我们在 Cavoodle.php 类中时,我的 IDE 通过使用 $this 键自动建议 Cavoodle 类本身的可用方法:

image 2023 10 23 13 30 31 781
Figure 2. Figure 4.6 – Cavoodle itself can access more functions than the Consumer class

Cavoodle.php 类中,你会发现这个 Cavoodle 对象可以访问 getColor()setColor() 方法。这是为什么呢?这是因为 Cavoodle 类扩展了 Dog.php 类,继承了 Dog.php 类的非私有方法—​因为我们已经声明 getColorsetColor 函数具有受保护的可见性,所以 Cavoodle 类可以使用这些方法。

访问器和修改器

既然我们已将 $sound$color 属性设置为私有,那么如何让消费者访问这些属性呢?为了读取数据,我们可以编写名为访问器的函数,返回存储在属性中的数据。要更改属性的值,我们可以创建称为突变器的函数来突变属性的数据。

要访问 Dog.php 类中的 $sound$color 属性,我们需要使用以下访问器:

  • getSound

  • getColor

要更改 Dog.php 类中 $sound$color 属性的值,我们需要以下修改器:

  • setSound

  • setColor

这些函数在 Dog.php 类中声明—​由于这些是函数,因此在将值存储到属性或返回给用户之前,可以添加额外的验证或逻辑更改。

在编写属性或函数时,一个好的做法是尽可能限制它们的可见性。首先声明私有,然后如果你认为子对象需要访问该函数或属性,则将可见性设置为受保护。这样,公开可用的方法和属性就会减少。

这将只允许你和其他使用你的类的开发者看到本应提供给消费者的函数。我曾写过并见过有很多方法的类,但最后发现除了主对象本身,其他对象并不打算使用这些方法。这样做还有助于防止消费者对象直接修改属性,从而保持对象的数据完整性。如果需要让消费者操作对象属性中存储的数据,用户可以使用突变器方法。要让用户从属性中读取数据,可以使用访问器。