PHP5 面向对象语言
声明一个类(Declaring A Class)
使用class关键字,提供一个类的名字,然后列出这个类的实例应该具备的方法和属性:
Class MyClass { ... //方法的列表 ... //属性的列表 }
new关键字和构造函数(The new Keyword And Constructors)
类的实例是使用new关键字创建的。
当你调用 new 的时候 PHP 会分派一个新的对象,并且从你定义的类中拷贝属性和方法,然后如果你有定义对象的构造函数的话,它将被自动调用。构造函数式一个命名为 __construct() 的方法,它在 new 关键字创建一个对象后自动运行。构造函数通常被用来自动地执行许多初始化操作,例如属性初始化。构造函数也接受参数,这种情况下,编写 new语句的时候,你还需要给构造函数的括号内传递函数的参数。
<?php class Person { function __construct($name) { $this->name = $name; } private $name; function getName() { return $this->name; } } $judy = new Person("Judy"); $joe = new Person("Joe"); print $judy->getName() . "<br />"; print $joe->getName(); ?>
输出结果:
Judy
Joe
析构函数(Destructors)
析构函数与构造函数的作用是相反的。它是在对象被注销的时候调用的(例如,没有任何变量引用对象)。
<?php class MyClass { function __destruct() { print "An object of type MyClass is being destroyed\n"; } } $obj = new MyClass(); $obj = NULL; ?>
输出结果:
An object of type MyClass is being destroyed
使用 $this 变量访问方法和属性(Accessing Methods And Properties Using The $this Variables)
在对象的方法执行的时候,PHP会自动定义一个叫 $this 的特殊变量,它表示一个对对象本身的引用。通过使用这个变量和 -> 符号,你可以引用改对象其他的方法和属性。
Public, protected 和 private 属性
OOP 中的一个关键范例就是封装和对象属性(也可以叫做成员变量)的访问和保护。
本类 子类 外部
Public 可以 可以 可以
Protected 可以 可以 不可以
Private 可以 不可以 不可以
静态属性(Static Properties)
类可以声明属性。每一个类的实例(例如:对象)拥有它自己的属性拷贝。
但是一个类也可以包含静态属性。和常规属性不一样的是,这些属性属于类本身而不属于类的任何实例。因此,它们经常被叫做类属性以便和对象或者实例的属性区分开来。你也可以把静态属性认为是存储在类当中的全局变量,而且你可以再任何地方通过类访问它们。
静态属性使用 static 关键字定义的:
Class MyClass { static $myStaticvar; }
要访问一个静态变量时,你必须用属性所在的类的名字来限制它
<?php class MyClass { static $myStatic; } MyClass::$myStatic++; print MyClass::$myStatic; ?>
输出结果:
1
如果你想在类里面的方法中访问到静态成员,你还可以通过在成员的名字前面加上特殊的类名 self 来访问它, self 是一个方法所属的类的缩写:
<?php class MyClass { static $myStatic = 0; function myMethod() { print self::$myStatic; } } $obj = new MyClass(); $obj->myMethod(); ?>
输出结果:
0
深度思考:(如果把 static 变成 public, 结果又如何?)
<?php class MyClass { static $idCounter = 0; public $uniqueId; function __construct() { self::$idCounter++; $this->uniqueId = self::$idCounter; } } $obj1 = new MyClass(); print $obj1->uniqueId . "<br />"; $obj2 = new MyClass(); print $obj2->uniqueId . "<br />"; ?>
输出结果:
1
2
静态方法(Static Methods)
与静态属性相似,PHP 支持把方法声明为静态的。也就是说静态方法是类的一部分而且不被限制到任何一个特定的对象实例和类的属性。因此,$this 在这些方法中将不能使用,但是类本身可以通过 self 访问。因为静态方法不能被限制到任何特定的对象,所以你可以不创建对象实例就通过 MyClass::Method() 语法调用它。你还可以再一个对象实例中通过 $this->method() 访问这些静态方法,但是在这些方法中 $this 是没有定义的。为了更加清楚些,你应该使用 self::method() 而不是 $this->method()。
<?php class MyClass { static function printHelloWorld() { print "Hello, World"; self::printNewLine(); } static function printNewLine() { print "<br />"; } } MyClass::printHelloWorld(); ?>
输出结果:
Hello, World
类的常量(Class Constants)
PHP5 中对类的封装进行了改进,你现在可以在类中定义常量。与静态成员类似,它们属于类本身而不是类的实例。类的常量总是对大小写敏感的。定义它们的语法很直观,而且访问类中的常量跟访问静态成员是类似的。
<?php class MyClass { const RED = "Red"; const GREEN = "Green"; const BLUE = "Blue"; function printBlue() { print self::BLUE; } } print MyClass::RED . "<br />"; $obj = new MyClass(); $obj->printBlue(); ?>
输出结果:
Red
Blue
克隆对象(Cloning Objects)
在创建一个对象的时候(使用 new 关键字),返回的值是一个指向对象的句柄,或者换言之,是对象的ID号。这跟 PHP4 是不一样的,在 PHP4 中返回的值就是对象的本身。这并不意味着调用方法或访问属性的语法被改变了,只是对象在复制的时候意义被更改了。
分析下面的代码;
<?php class MyClass { public $var = 1; } $obj1 = new MyClass(); $obj2 = $obj1; $obj2->var = 2; print $obj1->var; ?>
输出结果:
2
在 PHP4中, 这个代码会打印出1,因为 $obj2 被赋予了 $obj1 对象的值,因此 $obj2 是 $obj1 的一个拷贝,从而改变 $obj2 的时候 $obj1 不会被改变。但是在 PHP5 中,因为 $obj1 是一个对象句柄(它的id号),所以复制到 $obj2 的是一个句柄。因此,当改变 $obj2 的值得时候,你其实已经改变了 $obj1 指向的同一个对象。因此,运行这个代码片段后,结果是打印出2。
<?php class MyClass { public $var = 1; } $obj1 = new MyClass(); $obj2 = clone $obj1; $obj2->var = 2; print $obj1->var; ?>
输出结果:
1
下面的例子描述一个你可能要执行 __clone() 方法的一个典型的情况。假设你有一个对象,它存储着一个资源,例如一个文件句柄。你需要一个新的对象,而且它不能指向同一个句柄,而是打开一个新的文件从而拥有它自己的一个拷贝:
<?php class MyFile { private $file_name; private $file_handle = NULL; function setFileName($file_name) { $this->file_name = $file_name; } function openFileForReading() { $this->file_handle = fopen($this->file_name, "r"); } function __clone() { if($this->file_handle) $this->file_handle = fopen($this->file_name, "r"); } } ?>
多态(Polymorphism)
多态是 OOP 中最重要的部分了。 OOP 不是把函数和数据简单地集合起来,而是使用类和继承轻松地描述现实生活中的情况。它们还可以通过继承复用代码而轻松升级项目。另外,为了编写健壮的可扩展的代码,你通常需要尽量减少使用流程控制语句(例如 if() 语句)。多态将满足所有这些甚至更多的需求。
分析代码:
<?php /* 猫的类 */ class Cat { function miau(){ print "miau"; } } /* 狗的类 */ class Dog { function wuff(){ print "wuff"; } } function printTheRightSound($obj) { //php 5 提供了關鍵字 instanceof 用以判斷一個對象是否是某個類或接口的實例 if($obj instanceof Cat) { $obj->miau(); } else if ($obj instanceof Dog) { $obj->wuff(); } else { print "Error:未找到类"; } print "<br />"; } printTheRightSound(new Cat()); printTheRightSound(new Dog()); ?>
输出结果:
miau
wuff
可以很容易地看出这个例子是不可扩展的。如果想通过增加另外三种动物的声音来扩展的话,你可能不得不在 printTheRightSound() 函数中增加另外三个 else if 模块以便检查你的对象是其中哪一种新动物的实例,而且接下来还要增加代码来调用每一种发声的方法。
多态使用继承来解决这个问题。它可以让你继承一个父类,继承所有父类的方法和属性后创建“唯一”关系。
按照前一个例子,我们将创建一个新的类,名叫 animal。然后所有其它的动物从这个父类继承,从而为每一个特定的动物种类创建“唯一”关系。
<?php class Animal { function makeSound() { print "Error:方法应该在子类实现!"; } } class Cat extends Animal { function makeSound() { print "miau"; } } class Dog extends Animal { function makeSound() { print "wuff"; } } function printTheRightSound($obj) { if($obj instanceof Animal) { $obj->makeSound(); } else { print "Error:未找到类"; } print "<br />"; } printTheRightSound(new Cat()); printTheRightSound(new Dog()); ?>
输出结果:
miau
wuff
这个例子中增加多少种动物类型,都不需要对 printTheRightSound() 函数做任何更改,因为 instanceof Animal 可以检测所有这些类型, 而且 $obj->makeSound() 调用也是如此。
Parent:: 和 self( parent:: and self::)
PHP 支持两个类名的保留字让你在编写 OO 应用的时候可以轻松调用。self:: 指向当前的类,而且它通常用来访问静态成员,方法和常量。 Parent:: 指向父类,而且它经常被用来调用父类的构造函数和方法,它也可以用来访问父类的成员和常量。你应该用 parent:: 而不是父类的具体名字,这是为了让你可以方便地更改你的类的层次,因为你不用固定写入某个父类的名字。
下面的例子同时使用了 parent:: 和 self:: 来访问 Child 类和 Ancestor类:
<?php class Ancestor { const NAME = 'Ancestor'; function __construct() { print "In " . self::NAME . "constructor <br />"; } } class Child extends Ancestor { const NAME = 'Child'; function __construct() { parent::__construct(); print "In " . self::NAME . "constructor <br />"; } } $obj = new Child(); ?>
输出结果:
In Ancestorconstructor
In Childconstructor
instanceof 运算符( instanceof operator)
PHP 增加 instanceof 作为更符合使用习惯的运算符,并代替已有的 is_a() 内置函数(不推荐使用)。 instanceof 被当成一个逻辑二元运算符来使用:
<?php class Rectangle { public $name = __CLASS__; } class Square extends Rectangle { public $name = __CLASS__; } class Circle { public $name = __CLASS__; } function checkIfRectangle($shape) { if($shape instanceof Rectangle) { print $shape->name; print " is a rectangle <br />"; } } checkIfRectangle(new Square()); checkIfRectangle(new Circle()); ?>
输出结果:
Square is a rectangle
检测类是否存在
Abstract 方法和类(Abstract Methods And Class)
抽象方法,只要有一个方法被定义为 abstract,整个类也就需要声明为 abstract。
注意:与其他一些语言不同的是,你不能给抽象的方法定义一个默认的执行体。但是在 PHP 中,一个方法可以是 abstract(没有代码)的,或者可以是完整定义的。
接口 (Interfaces)
类的继承让你可以描述几个类之间的父与子的关系。声明一个接口与声明一个类是类似的,但是它只包含函数原型(没有执行体)和常量。任何一个 “implements” 这个接口的类将自动获得这个接口定义的常量并且作为实现的类需要给接口的函数原型提供函数定义(除非你定义实现接口的类为 abstract), 接口的函数都是 abstract 的。
通过下面的语法来实现一个接口:
Class A implements b, c, … { }
下面的例子定义了名叫 Loggable 的接口,它让其他类来实现并通过 Mylog() 定义需要记录哪些信息的日志。 那些没有实现这个接口的类的对象传递给 Mylog() 函数将会打印出错误的信息:
<?php interface Loggable { function logString(); } class Person implements Loggable { private $name, $address, $idNumber, $age; function logString() { return "class person: name = $this->name, ID = $this->idNumber <br />"; } } class Product implements Loggable { private $name, $price, $expiryDate; function logString() { return "class Product: name = $this->name, price = $this->price <br />"; } } function MyLog($obj) { if($obj instanceof Loggable) { print $obj->logString(); } else { print "Error: object doesn't support Loggable interface <br />"; } } $person = new Person(); $product = new Product(); MyLog($person); MyLog($product); ?>
输出结果:
class person: name = , ID =
class Product: name = , price =
接口的继承(Inheritance of Interfaces)
接口是可以从别的接口继承来的。继承接口的语法与继承类的语法类似,但是接口可以多重继承:
interface I1 extends I2, I3, … { }
与类实现接口类似,一个接口只能继承与自己互相不冲突的接口(也就是说如果I2定义了在I1已经定义的方法或者常量,你将接收到报错信息)。
final 方法(final Methods)
很多时候你需要确保一个方法不能被继承类改写。为此,PHP 支持 final 访问控制符来声明一个方法是不可重写的。
下面的例子不是一个合法的 PHP 脚本,因为它试图重写一个 final 方法:
<?php class MyBaseClass { final function idGenerator() { return $this->id++; } protected $id = 0; } class MyConcreteClass extends MyBaseClass { function idGenerator() { return $this->id += 2; } } ?>
输出结果:
Fatal error: Cannot override final method MyBaseClass::idGenerator() in D:\wamp\www\php5\01\lx16.php on line 17
这个脚本不会执行,因为在 MyBaseClass 中把 idGenerator() 定义为 final, 它不许继承的类重写 idGenerator() 并且修改id 增长的逻辑。
final类(final Classes)
与 final 方法类似,你还可以定义一个类为 final。 这么做将不可以其他类继承此类。
final class MyBaseClass { } class MyConcreteClass extends MyBaseClass { }
MyBaseClass 已经被声明为 final, MyConcreteClass 不能继承它。 因此,这个代码的执行失败。
__toString()方法(__toString() Method)
考虑下面的代码:
<?php class Person { function __construct($name) { $this->name = $name; } private $name; } $obj = new Person("小明"); print $obj; ?>
输出结果:
Catchable fatal error: Object of class Person could not be converted to string in D:\wamp\www\php5\01\lx17.php on line 12
程序将运行错误!!
PHP 为你提供一个叫__toString() 的函数,你可以用它来返回表示对象的字符串信息,而且一旦定义它,打印命令将调用它并打印出返回的字符串。
<?php class Person { private $name; function __construct($name) { $this->name = $name; } function __toString() { return $this->name; } } $obj = new Person("小明"); print $obj; ?>
输出结果:
小明
目前__toString() 只能被 print 和 echo 语言结构调用。
异常处理(Exception Handling)
大多数现代的语言都支持一些各自的很流行的 try/catch/throw 异常处理范例。 try/catch 是一个闭合的语言结构,它们保护被其包含的源代码而且会告诉 PHP,“我正在处理发生在这些代码的异常”。异常或者错误在被检测到的时候“抛出”并且 PHP 事实地去搜索它所调用的堆栈,查看是否存在一个相应的 try/catch 结构可以来处理这个异常。
这种方法有许多好处。首先,你不用放置 if() 语句在任何异常有可能出现的地方;因此可以减少很多代码。你可以把整个部分的代码都装入 try/catch 结构并且当一个错误发生时处理错误。另外,在你用 throw 语句检测到一个错误后,可以轻松地传递到代码中需要负责处理异常的地方并继续让程序执行下去,因为 throw 展开了函数调用的堆栈知道它检测到一个合适的 try/catch 块。
Try/catch 的语法如下:
Try { … // 会抛出一个异常的代码 } catch (FirstExceptionClass $exception) { … // 会处理这个异常的代码 } catch (SecondExceptionClass) { }
Try{ }结构装入的代码可以抛出一个异常,后面可以加上一系列的 catch 语句,每一个 catch 声明哪个异常应该调用并且在 catch 块中异常的对象应该用什么变量名来访问。
<?php class NullHandleException extends Exception { function __construct($message) { parent::__construct($message); } } function printObject($obj) { if($obj == NULL) { throw new NullHandleException("printObject received NULL object"); } print $obj . "<br />"; } class MyName { function __construct($name) { $this->name = $name; } function __toString() { return $this->name; } private $name; } try { printObject(new MyName("Bill")); printObject(NULL); printObject(new MyName("jane")); } catch (NullHandleException $exception) { print $exception->getMessage(); print " in file " . $exception->getFile(); print " on line " . $exception->getLine() . "<br />"; } catch (Exception $exception) { //这里将不会被执行 } ?>
输出结果:
Bill
printObject received NULL object in file D:\wamp\www\php5\01\lx19.php on line 13
__autoload()
当编写面向对象的代码时,人们经常习惯于把每一个类放到它自己的源文件中。这样做的好处是可以很轻松地找到类在哪里,而且这样还可以将包含的代码数量最小化,因为你只需引入实际需要的类。这样做的缺陷在于你不得不经常包含大量的源文件,从而让人头疼,而且经常导致包含的太多的文件和一个头疼的代码维护问题。如果你定义一个 __autoload() 可以解决这个问题,它不需要你包含将要使用的类。如果你顶一个 __autoload() 函数,而且当你访问一个还未存在的类时,它将被调用并且把类的名字作为它的参数。这样做给你一个实时包含类的可能性。 如果你成功地包含了该类,你的源码会继续执行,就像这个类已经定义了一样。如果没有成功包含该类,脚本引擎会产生一个类不存在的严重错误。
MyClass.php
<?php class MyClass { function printHelloWorld() { print "Hello, World <br />"; } } ?>
general.php
<?php function __autoload($class_name) { require_once("./$class_name.php"); } ?>
main.php
<?php require_once "./general.php"; $obj = new MyClass(); $obj->printHelloWorld(); ?>
输出结果:
Hello, World