PHP 中的面向对象编程 II
在 PHP 教程的这一章中,我们将继续描述 PHP 中的 OOP。
PHP static
关键字
我们可以将类属性和方法声明为static
。 static
属性和方法不属于该类的实例。 他们属于阶级本身。 可通过范围解析运算符::
访问它们。
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 脚本中,我们定义了两个自定义对象:Object
和Color
。 Object
对象将具有对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。大小不同:small
与big
。 但是,这两个实例的颜色对象的红色部分相同: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 中的面向对象编程。