跳转至

PHP 中的面向对象编程 II

原文: https://zetcode.com/lang/php/oopii/

在 PHP 教程的这一章中,我们将继续描述 PHP 中的 OOP。

PHP static关键字

我们可以将类属性和方法声明为staticstatic属性和方法不属于该类的实例。 他们属于阶级本身。 可通过范围解析运算符::访问它们。

staticmethod.php

<?php

class Sys {

    public static function println($string) {

        echo "$string\n";
    }
}

Sys::println("PHP");
Sys::println("PERL");
Sys::println("Python");
Sys::println("Pike");

在上面的 PHP 脚本中,我们有一个静态的println()方法。 它打印一个字符串并开始新的一行。 此示例受 Java 语言启发。

Sys::println("PHP");

我们不需要对象来调用println()方法。 我们通过指定类名称,后跟双冒号运算符和方法名称来调用static方法。

$ php static1.php 
PHP
PERL
Python
Pike

这是脚本的输出。

staticvariable.php

<?php

class Math {

    public static $PI = 3.14159265359;
}

echo Math::$PI . "\n";

现在,我们有一个带有static变量的示例。

echo Math::$PI . "\n";

我们通过指定类名,范围解析运算符和变量名来访问变量。

PHP final关键字

最终方法不能被覆盖,最终类不能被扩展。 final关键字取决于应用的设计。 某些类不应扩展,某些方法不应重写。 此行为由final关键字强制执行。

finalmethod.php

<?php

class Base {

    final function say() {
        echo "Base class";
    }
}

class Derived extends Base {

    function say() {
        echo "Derived class";
    }
}

该 PHP 脚本无法编译。 我们收到一个错误“无法覆盖最终方法Base::say()”。

finalclass.php

<?php

final class Math {

    static function getPI() {
        return 3.141592;
    }
}

class DerivedMath extends Math {

    function say() {
        echo "DerivedMath class";
    }
}

在先前的 PHP 脚本中,我们有一个原型基础Math类。 该类的唯一目的是为程序员提供一些有用的方法和常量。 (出于简单起见,在我们的例子中,我们只有一种方法。)它不是为了扩展而创建的。 为了防止不知情的其他程序员从此类中派生,创建者创建了final类。 如果您尝试运行此 PHP 脚本,则会出现以下错误:“致命错误:类DerivedMath可能不会从最终类(Math)继承”。

PHP 深拷贝与浅拷贝

数据复制是编程中的重要任务。 对象是 OOP 中的复合数据类型。 对象中的成员字段可以按值或按引用存储。 可以以两种方式执行复制。

浅表副本将所有值和引用复制到新实例中。 引用所指向的数据不会被复制; 仅指针被复制。 新的引用指向原始对象。 对引用成员的任何更改都会影响两个对象。

深拷贝将所有值复制到新实例中。 如果成员存储为引用,则深层副本将对正在引用的数据执行深层副本。 创建引用对象的新副本,并存储指向新创建的对象的指针。 对这些引用对象的任何更改都不会影响该对象的其他副本。 深拷贝是完全复制的对象。

在 PHP 中,我们有一个copy关键字,默认情况下会执行浅表复制。 它调用对象的__clone()方法。 我们可以实现创建自定义深层副本的方法。 在 PHP 中,所有对象都是通过引用分配的。

接下来的两个示例对对象执行浅复制和深复制。

shallowcopy.php

<?php

class Object {

    public $id;
    public $size;
    public $color;

    function __construct($id, $size, $color) {

        $this->id = $id;
        $this->size = $size;
        $this->color = $color;
    }
}

class Color {

    public $red;
    public $green;
    public $blue;

    function __construct($red, $green, $blue) {

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

$color = new Color(23, 42, 223);

$object1 = new Object(23, "small", $color);
$object2 = clone $object1;

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

print_r($object1);
print_r($object2);

在上面的 PHP 脚本中,我们定义了两个自定义对象:ObjectColorObject对象将具有对Color对象的引用。

$color = new Color(23, 42, 223);

我们创建Color对象的实例。

$object1 = new Object(23, "small", $color);

创建对象对象的实例。 它将Color对象的实例传递给其构造器。

$object2 = clone $object1;

我们执行Object对象的浅表副本。

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

在这里,我们修改克隆对象的成员字段。 我们增加 id,更改颜色对象的红色部分,并将大小更改为big

print_r($object1);
print_r($object2);

我们使用print_r()函数比较结果。

$ php shallowcopy.php 
Object Object
(
    [id] => 23
    [size] => small
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)
Object Object
(
    [id] => 24
    [size] => big
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)

我们可以看到 ID 不同:23 与 24。大小不同:smallbig。 但是,这两个实例的颜色对象的红色部分相同:255。更改克隆对象的成员值不会影响原始对象。 更改引用对象的成员也影响了原始对象。 换句话说,两个对象都引用内存中的同一颜色对象。

要更改此行为,我们接下来将做一个深层复制。

deepcopy.php

<?php

class Object {

    public $id;
    public $size;
    public $color;

    function __construct($id, $size, $color) {

        $this->id = $id;
        $this->size = $size;
        $this->color = $color;
    }

    function __clone() {

        $red = $this->color->red;
        $green = $this->color->green;
        $blue = $this->color->blue;
        $this->color = new Color($red, $green, $blue);
    }
}

class Color {

    public $red;
    public $green;
    public $blue;

    function __construct($red, $green, $blue) {

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

$color = new Color(23, 42, 223);

$object1 = new Object(23, "small", $color);
$object2 = clone $object1;

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

print_r($object1);
print_r($object2);

在此 PHP 脚本中,我们实现了__clone()方法。

function __clone() {
    $red = $this->color->red;
    $green = $this->color->green;
    $blue = $this->color->blue;
    $this->color = new Color($red, $green, $blue);
}

__clone()方法内部,我们复制红色,绿色和蓝色成员字段并创建一个新的Color对象。 现在,$color字段指向另一个Color对象。

$ php deepcopy.php 
Object Object
(
    [id] => 23
    [size] => small
    [color] => Color Object
        (
            [red] => 23
            [green] => 42
            [blue] => 223
        )

)
Object Object
(
    [id] => 24
    [size] => big
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)

现在,引用的Color对象的红色部分不相同。 原始对象保留了其先前的 23 值。

PHP 异常

异常是为处理异常的发生而设计的,这些特殊情况会改变程序执行的正常流程。 引发或引发异常并引发异常。

在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 互联网连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃。 为避免发生这种情况,我们必须应对可能发生的所有可能的错误。 为此,我们可以使用异常处理。

从 PHP 5 开始,可以使用异常。大多数 PHP 错误仍然使用旧的错误报告,而不是异常。 使用set_error_handler(),我们可以解决此问题。

zerodiv.php

<?php

set_error_handler("error_handler");

function error_handler($errno, $errstring, $errfile, $line, $trace) {
    throw new ErrorException($errstring, $errno, 0,  $errfile, $line);
}

try {

    $a = 0;
    $b = 32;
    $c = $b / $a;
} catch(ErrorException $e) {

   echo "Error occurred\n";
   echo $e->getMessage(), "\n";
}

在上面的 PHP 脚本中,我们有意将数字除以零。 这会导致错误。 该错误也不是异常,并且不会被catch关键字捕获。

set_error_handler("error_handler");

set_error_handler()函数设置用户定义的错误处理器函数。

function error_handler($errno, $errstring, $errfile, $line, $trace) {
    throw new ErrorException($errstring, $errno, 0,  $errfile, $line);
}

set_error_handler()函数内部,我们抛出了ErrorException。 稍后,此异常由catch关键字捕获。

try {

    $a = 0;
    $b = 32;
    $c = $b / $a;
}

我们正在检查错误的代码放在try关键字之后的块中。

} catch(Exception $e) {
   echo $e->getMessage();
}

catch关键字用于捕获异常。 要了解更多信息,我们在异常对象上调用getMessage()方法。

$ php zerodiv.php 
Error occurred
Division by zero

这是我们的 PHP 脚本的输出。

Exception是所有异常的基类。 我们可以创建派生自该基类的异常。

myexception.php

<?php

define("LIMIT", 333);

class BigValueException extends Exception {

    public function __construct($message) {
        parent::__construct($message);
    }
}

$a = 34325;

try {
    if ($a > LIMIT) {
        throw new BigValueException("Exceeded the maximum value allowed\n");   
    }
} catch (BigValueException $e) {
    echo $e->getMessage();   
}

假设我们处于无法处理大量数字的情况。

define("LIMIT", 333);

大于此常量的数字在我们的 PHP 脚本中被视为big

class BigValueException extends Exception {

我们有一个BigValueException类。 此类通过extends关键字从Exception类派生。

public function __construct($message) {
    parent::__construct($message);
}

在构造器内部,我们称为父级的构造器。

if ($a > LIMIT) {
    throw new BigValueException("Exceeded the maximum value allowed\n");   
}

如果该值大于限制,则抛出自定义异常。 我们给异常消息"Exceeded the maximum value allowed\n"

} catch (BigValueException $e) {
    echo $e->getMessage();   
}

我们捕获到异常并将其消息打印到控制台。

PHP 构造器重载

PHP 不支持直接的构造器重载。 换句话说,每个类只能定义一个构造器。 许多已经知道 Java 或 C# 语言的程序员都在寻找 PHP 中的类似功能。 我们有两种方法可以处理此问题。

第一种解决方案基于func_get_args()函数。 第二种解决方案使用工厂模式。

constructors.php

<?php

class Book {

    private $author = "not specified";
    private $title = "not specified";
    private $year = "not specified";

    public function __construct() {

        $args = func_get_args();

        foreach(["title", "author", "year"] as $item) {

            if(empty($args)) {
                break;
            }

            $this->$item = array_shift($args);
        }
    }

    public function __toString() {
        return "Author: $this->author\nTitle: $this->title\nPublished: $this->year\n\n";
    }
}

$book1 = new Book("Stephen Prata", "C Primer Plus");
echo $book1;

$book2 = new Book("Joshua Bloch", "Effective Java", 2008);  
echo $book2;

在上面的脚本中,我们有一个Book类。 我们使用 2 和 3 参数实例化该类。

private $author = "not specified";
private $title = "not specified";
private $year = "not specified";

我们定义了三个成员字段。 它们的初始值为"not specified"

$args = func_get_args();

func_get_args()函数返回一个包含函数的参数列表的数组。 这样的想法是:构造器中的代码是动态的; 它取决于传递给它的参数。

foreach(["title", "author", "year"] as $item) {

我们使用foreach关键字浏览所有成员字段。

$this->$item = array_shift($args);

构造器中最基本的任务之一是初始化类的成员字段。 这是通过上面的代码行完成的。 array_shift()函数从数组中删除第一项并返回它。

$book1 = new Book("Stephen Prata", "C Primer Plus");
...
$book2 = new Book("Joshua Bloch", "Effective Java", 2008);

我们有两个不同的构造器。 第一个带有 2 个参数,第二个带有 3 个参数。

$ php constructors.php
Author: C Primer Plus
Title: Stephen Prata
Published: not specified

Author: Effective Java
Title: Joshua Bloch
Published: 2008

这是脚本的结果。

下一个代码示例使用工厂模式模拟构造器重载。 它是 OOP 中的创建模式之一。 该模式创建对象时未指定要创建的对象的确切类。 更一般地,术语工厂方法通常用于指代主要目的是创建对象的任何方法。

factory.php

<?php

class Cat {

    private $name = "unspecified";
    private $age = "unspecified";

    public static function withName($name) {

        $cat = new Cat();
        $cat->name = $name;

        return $cat;
    }

    public static function withAge($age) {

        $cat = new Cat();
        $cat->age = $age;

        return $cat;
    }

    public static function fullCat($name, $age) {

        $cat = new Cat();
        $cat->name = $name;
        $cat->age = $age;

        return $cat;
    }

    public function __toString() {
        return "Name: $this->name, Age: $this->age\n";
    }
}

$cici = Cat::withName("Cici");
echo $cici;

$missy = Cat::withAge(6);
echo $missy;

$lucky = Cat::fullCat("Lucky", 4);
echo $lucky;

上面的 PHP 脚本中有一个Cat工厂类。 它具有三种不同的静态函数。 它们每个都返回一个特定的Cat对象。

private $name = "unspecified";
private $age = "unspecified";

我们有两个成员字段。 它们的初始值为"not specified"

public static function withName($name) {
    $cat = new Cat();
    $cat->name = $name;

    return $cat;
}

这是静态的withName()函数。 此函数创建Cat类的实例。 它设置name成员字段并返回对象。

$cici = Cat::withName("Cici");
echo $cici;

我们使用其中一种工厂方法创建猫的实例。 我们回荡对象。 即调用该类的__toString()方法。

$ php factory.php 
Name: Cici, Age: unspecified
Name: unspecified, Age: 6
Name: Lucky, Age: 4

脚本的输出。

在 PHP 教程的这一部分中,我们继续讨论 PHP 中的面向对象编程。



回到顶部