在 PHP 教程的这一章中,我们将继续描述 PHP 中的 OOP。
PHP static关键字
我们可以将类属性和方法声明为static。 static属性和方法不属于该类的实例。 他们属于阶级本身。 可通过范围解析运算符::访问它们。
staticmethod.php
<?phpclass 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.phpPHPPERLPythonPike
这是脚本的输出。
staticvariable.php
<?phpclass Math {public static $PI = 3.14159265359;}echo Math::$PI . "\n";
现在,我们有一个带有static变量的示例。
echo Math::$PI . "\n";
我们通过指定类名,范围解析运算符和变量名来访问变量。
PHP final关键字
最终方法不能被覆盖,最终类不能被扩展。 final关键字取决于应用的设计。 某些类不应扩展,某些方法不应重写。 此行为由final关键字强制执行。
finalmethod.php
<?phpclass Base {final function say() {echo "Base class";}}class Derived extends Base {function say() {echo "Derived class";}}
该 PHP 脚本无法编译。 我们收到一个错误“无法覆盖最终方法Base::say()”。
finalclass.php
<?phpfinal 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
<?phpclass 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.phpObject 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
<?phpclass 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.phpObject 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
<?phpset_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.phpError occurredDivision by zero
这是我们的 PHP 脚本的输出。
Exception是所有异常的基类。 我们可以创建派生自该基类的异常。
myexception.php
<?phpdefine("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
<?phpclass 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.phpAuthor: C Primer PlusTitle: Stephen PrataPublished: not specifiedAuthor: Effective JavaTitle: Joshua BlochPublished: 2008
这是脚本的结果。
下一个代码示例使用工厂模式模拟构造器重载。 它是 OOP 中的创建模式之一。 该模式创建对象时未指定要创建的对象的确切类。 更一般地,术语工厂方法通常用于指代主要目的是创建对象的任何方法。
factory.php
<?phpclass 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.phpName: Cici, Age: unspecifiedName: unspecified, Age: 6Name: Lucky, Age: 4
脚本的输出。
在 PHP 教程的这一部分中,我们继续讨论 PHP 中的面向对象编程。
