14.3 面向对象的高级应用
视频讲解:光盘TMlx14面向对象的高级应用.exe
经过14.2节的学习,相信读者对PHP的面向对象已经有了一定的了解。下面来学习一些面向对象的高级应用。
14.3.1 final关键字
final,中文含义是“最终的”“最后的”。被final修饰过的类和方法就是“最终的版本”。
如果有一个类的格式为:
final class class_name{ … }
说明该类不可以再被继承,也不能再有子类。
如果有一个方法的格式为:
final function method_name()
说明该方法在子类中不可以进行重写,也不可以被覆盖。
【例14.12】本例为SportObject类设置关键字final,并生成一个子类MyBook。可以看到程序报错,无法执行。实例代码如下:(实例位置:光盘TMsl1412)
<?php final class SportObject{ //final类SportObject function __construct(){ //构造方法 echo 'initialize object'; } } class MyBook extends SportObject{ //创建SportObject类的子类Mybook static function exam(){ //子类中的方法 echo "You can't see me"; } } MyBook::exam(); //调用子类方法 ?>
结果为:Fatal error: Class MyBook may not inherit from final class (SportObject) in E:wampwwwTMsl1412index.php on line 30
14.3.2 抽象类
抽象类是一种不能被实例化的类,只能作为其他类的父类来使用。抽象类使用abstract关键字来声明,格式为:
abstract class AbstractName{ … }
抽象类和普通类相似,包含成员变量、成员方法。两者的区别在于,抽象类至少要包含一个抽象方法。抽象方法没有方法体,其功能的实现只能在子类中完成。抽象方法也是使用abstract关键字来修饰的,它的格式为:
abstract function abstractName();
注意
在抽象方法后面要有分号“; ”。
抽象类和抽象方法主要应用于复杂的层次关系中,这种层次关系要求每一个子类都包含并重写某些特定的方法。举一个例子,中国的美食是多种多样的,有吉菜、鲁菜、川菜、粤菜等。每种菜系使用的都是煎、炒、烹、炸等手法,只是在具体的步骤上各有各的不同。如果把中国美食当作一个大类Cate,下面的各大菜系就是Cate的子类,而煎、炒、烹、炸则是每个类中都有的方法。每个方法在子类中的实现都是不同的,在父类中无法规定。为了统一规范,不同子类的方法要有一个相同的方法名:decoct(煎)、stir_fry(炒)、cook(烹)、fry(炸)。
【例14.13】下面实现一个商品抽象类CommodityObject,该抽象类包含一个抽象方法service。为抽象类生成两个子类MyBook和MyComputer,分别在两个子类中实现抽象方法。最后实例化两个对象,调用实现后的抽象方法,输出结果。实例代码如下:(实例位置:光盘TMsl1413)
<?php abstract class CommodityObject{ //定义抽象类 abstract function service($getName, $price, $num); //定义抽象方法 } class MyBook extends CommodityObject{ //定义子类,继承抽象类 function service($getName, $price, $num){ //定义方法 echo’您购买的商品是’.$getName.',该商品的价格是:'.$price.' 元。'; echo’您购买的数量为:'.$num.' 本。'; echo ’如发现缺页,损坏请在3日内更换。'; } } class MyComputer extends CommodityObject{ //定义子类继承父类 function service($getName, $price, $num){ //定义方法 echo’您购买的商品是’.$getName.',该商品的价格是:'.$price.' 元。'; echo’您购买的数量为:'.$num.' 台。'; echo ’如发生非人为质量问题,请在3个月内更换。'; } } $book=new MyBook(); //实例化子类 $computer=new MyComputer(); //实例化子类 $book->service('《PHP从入门到精通》',85,3); //调用方法 echo '<p>'; $computer->service('XX笔记本’,8500,1); //调用方法 ?>
运行结果如图14.7所示。
图14.7 抽象类
14.3.3 接口的使用
继承特性简化了对象、类的创建,增加了代码的可重性。但PHP只支持单继承。如果想实现多重继承,就要使用接口。PHP可以实现多个接口。
接口类通过interface关键字来声明,并且类中只能包含未实现的方法和一些成员变量,格式如下:
interface InterfaceName{ function interfaceName1(); function interfaceName2(); … }
注意
不要用public以外的关键字来修饰接口中的类成员,对于方法,不写关键字也可以。这是一个接口类自身的属性决定的。
子类是通过implements关键字来实现接口的,如果要实现多个接口,那么每个接口之间应使用逗号“, ”连接。而且所有未实现的方法需要在子类中全部实现,否则PHP将会出现错误。格式如下:
class SubClass implements InterfaceName1, InterfaceName2{ function interfaceName1(){ …//功能实现 } function interfaceName2(){ …//功能实现 } … }
【例14.14】本例首先声明了两个接口MPopedom和MPurview,接着声明了两个类Member和Manager,其中Member类继承了MPopedom接口,Manager继承了MPopedom和MPurview接口。分别实现各自的成员方法后,实例化两个对象$member和$manager。最后调用实现后的方法。实例代码如下:(实例位置:光盘TMsl1414)
<?php /* 声明接口MPopedom */ interface MPopedom{ function popedom(); } /* 声明接口MPurview */ interface MPurview{ function purview(); } /* 创建子类Member,实现一个接口MPurview */ class Member implements MPurview{ function purview(){ echo ’会员拥有的权限。'; } } /* 创建子类Manager,实现多个接口MPurview和MPopedom */ class Manager implements MPurview, MPopedom{ function purview(){ echo ’管理员拥有会员的全部权限。'; } function popedom(){ echo ’管理员还有会员没有的权限’; } } $member=new Member(); //类Member实例化 $manager=new Manager(); //类Manager实例化 $member->purview(); //调用$member对象的purview方法 echo '<p>'; $manager->purview(); //调用$manager对象的purview方法 $manager->popedom(); //调用$manager对象的popedom方法 ?>
运行结果如图14.8所示。
图14.8 应用接口
通过上面的实例可以发现,抽象类和接口实现的功能十分相似。抽象类的优点是可以在抽象类中实现公共的方法,而接口则可以实现多继承。至于何时使用抽象类和接口就要看具体实现了。
14.3.4 克隆对象
1.关键字clone
在PHP 4中,对象被当作普通的数据类型来使用。如果想引用对象,需要使用“&”来声明,否则会按照PHP 4的默认方式来按值传递对象。下面结合实例说明。
【例14.15】本例首先实例化一个SportObject类的对象$book1, $book1的默认值为book,然后将对象$book1使用普通数据类型的赋值方式给对象$book2赋值。改变$book2的值为computer,再输出对象$book1的值。实例代码如下:(实例位置:光盘TMsl1415)
<?php class SportObject{ //类SportObject private$object_type='book'; //声明私有变量$object_type,并赋初值为book public function setType($type){ //声明成员方法setType,为变量$object_type赋值 $this -> object_type = $type; } public function getType(){ //声明成员方法getType,返回变量$object_type的值 return $this -> object_type; } } $book1=new SportObject(); //实例化对象$book1 $book2=$book1; //使用普通数据类型的方法给对象$book2赋值 $book2->setType('computer'); //改变对象$book2的值 echo’对象$book1的值为:'.$book1->getType(); //输出对象$book1的值 ?>
上面的实例在PHP 5中的返回值为“对象$book1的值为:computer”,因为$book2只是$book1的一个引用;而在PHP 4中的返回值是“对象$book1的值为:book”,因为对象$book2是$book1的一个备份。
在PHP 5中如果需要将对象复制,也就是克隆一个对象,需要使用关键字clone来实现。克隆对象的格式为:
$object1 = new ClassName(); $object2 = clone $object1;
将例14.15中的$book2=$book1修改为$book2=clone $book1,其他不变,即可返回PHP 4中的结果。
2.__clone方法
有时除了单纯地克隆对象外,还需要克隆出来的对象可以拥有自己的属性和行为。这时就可以使用__clone方法来实现。__clone方法的作用是:在克隆对象的过程中,调用__clone方法,可以使克隆出来的对象保持自己的一些行为及属性。
【例14.16】本例将例14.15的代码做一些修改。在对象$book1中创建__clone方法,该方法实现的功能是将变量$object_type的默认值从book修改为computer。使用对象$book1克隆出对象$book2,输出$book1和$book2的$object_type值,查看最终的结果。实例代码如下:(实例位置:光盘TMsl1416)
<?php class SportObject{ //类SportObject private$object_type='book'; //声明私有变量$object_type,并赋初值为book public function setType($type){ //声明成员方法setType,为变量$object_type赋值 $this -> object_type = $type; } public function getType(){ //声明成员方法getType,返回变量$object_type的值 return $this -> object_type; } public function clone(){ //声明__clone方法 $this->object type='computer'; //将变量$object_type的值修改为computer } } $book1=new SportObject(); //实例化对象$book1 $book2=clone$book1; //使用普通数据类型的方法给对象$book2赋值 echo’对象$book1的变量值为:'.$book1->getType(); //输出对象$book1的值 echo '<br>'; echo ’对象$book2的变量值为:'.$book2 -> getType(); ?>
运行结果如图14.9所示。
图14.9 __clone方法
不难看出,对象$book2克隆了对象$book1的全部行为及属性,也拥有了属于自己的成员变量值。
14.3.5 对象比较
通过克隆对象,相信读者已经理解表达式$Object2 = $Object1和$Object2 = clone $Object1所表示的不同含义。但在实际开发中,还需判断两个对象之间的关系是克隆还是引用,这时可以使用比较运算符“==”和“===”。两个等号“==”是比较两个对象的内容,3个等号“===”是比较对象的引用地址。
【例14.17】本例首先实例化一个对象$book,然后分别创建一个克隆对象和引用,使用“==”和“===”判断它们之间的关系,最后输出结果。实例代码如下:(实例位置:光盘TMsl1417)
<?php /* SportObject类 */ class SportObject{ private $name; function __construct($name){ $this -> name = $name; } } /***********************/ $book=new SportObject('book'); //实例化一个对象$book $cloneBook=clone$book; //克隆对象$cloneBook $referBook=$book; //引用对象$referBook if($cloneBook==$book){ //使用==比较克隆对象和原对象 echo ’两个对象的内容相等<br>'; } if($referBook===$book){ //使用===比较引用对象和原对象 echo ’两个对象的引用地址相等<br>'; } ?>
结果为:两个对象的内容相等
两个对象的引用地址相等
14.3.6 对象类型检测
instanceof操作符可以检测当前对象属于哪个类。一般格式为:
ObjectName instanceof ClassName
【例14.18】本例首先创建两个类,一个基类(SportObject)与一个子类(MyBook)。实例化一个子类对象,判断对象是否属于该子类,再判断对象是否属于基类。实例代码如下:(实例位置:光盘TMsl1418)
<?php class SportObject{} //创建空类SportObject class MyBook extends SportObject{ //创建子类MyBook private $type; } $cBook=new MyBook(); //实例化对象$cBook if($cBook instanceof MyBook) //判断对象是否属于类MyBook echo ’对象$cBook属于MyBook类<br>'; if($cBook instanceof SportObject) //判断对象是否属于类SportObject echo ’对象$Book属于SportObject类<br>'; ?>
结果为:对象$cBook属于MyBook类
对象$cBook属于SportObject类
14.3.7 魔术方法
PHP中有很多以两个下划线开头的方法,如前面已经介绍过的__construct、__destruct和__clone,这些方法被称为魔术方法。本节中将会学习到其他一些魔术方法。
注意
PHP中保留了所有以“__”开头的方法,所以只能使用在PHP文档中已有的这些方法,不要自己创建。
1.__set和__get方法
这两个魔术方法的作用分别为:
当程序试图写入一个不存在或不可见的成员变量时,PHP就会执行__set方法。__set方法包含两个参数,分别表示变量名称和变量值,两个参数不可省略。
当程序调用一个未定义或不可见的成员变量时,可以通过__get方法来读取变量值。__get方法有一个参数,表示要调用的变量名。
注意
如果希望PHP调用这些魔术方法,首先必须在类中进行定义,否则PHP不会执行未创建的魔术方法。
【例14.19】本例首先声明类SportObject,在类中创建一个私有变量$type和两个魔术方法__set、__get,接着实例化一个对象$MyComputer,先对已存在的私有变量进行赋值和调用,再对未声明的变量$name进行调用,最终查看输出结果。实例代码如下:(实例位置:光盘TMsl1419)
<?php class SportObject{ //类SportObject private$type=''; //私有变量$type public function__get($name){ //声明魔术方法__get if(isset($this->$name)){ //判断变量是否被声明 echo ’变量’.$name.’的值为:'.$this -> $name.'<br>'; }else{ echo ’变量’.$name.’未定义,初始化为0<br>'; $this->$name=0; //如果未被声明,则对变量初始化 } } public function__set($name, $value){ //声明魔术方法__set if(isset($this->$name)){ //判断变量是否定义 $this -> $name = $value; echo ’变量’.$name.’赋值为:'.$value.'<br>'; }else{ $this->$name=$value; //如果未定义,继续对变量进行赋值 echo’变量’.$name.’被初始化为:'.$value.'<br>'; //输出警告信息 } } } $MyComputer=new SportObject(); //实例化对象$MyComputer $MyComputer->type='DIY'; //给变量赋值 $MyComputer->type; //调用变量$type $MyComputer->name; //调用变量$name ?>
运行结果如图14.10所示。
图14.10 __set和__get方法
注意
魔术方法均用public关键字修饰。
2.__call方法
__call方法的作用是:当程序试图调用不存在或不可见的成员方法时,PHP会先调用__call方法来存储方法名及其参数。__call方法包含两个参数,即方法名和方法参数。其中,方法参数是以数组形式存在的。
【例14.20】本例声明一个类SportObject,类中包含两个方法,即myDream和__call。实例化对象$MyLife需调用两个方法,一个是类中存在的myDream方法,一个是不存在的mDream方法。实例代码如下:(实例位置:光盘TMsl1420)
<?php /* 类SportObject */ class SportObject{ public function myDream(){ //方法myDream echo ’调用的方法存在,直接执行此方法。<p>'; } public function__call($method, $parameter){ //__call方法 echo ’如果方法不存在,则执行__call()方法。<br>'; echo’方法名为:'.$method.'<br>'; //输出第一个参数,即方法名 echo ’参数有:'; var_dump($parameter); //输出第二个参数,是一个参数数组 } } $exam=new SportObject(); //实例化对象$exam $exam->myDream(); //调用存在的方法myDream $exam->mDream('how', 'what', 'why'); //调用不存在的方法mDream ?>
运行结果如图14.11所示。
图14.11 __call方法
3.__sleep和__wakeup方法
使用serialize()函数可以实现序列化对象。就是将对象中的变量全部保存下来,对象中的类则只保存类名。在使用serialize()函数时,如果实例化的对象包含__sleep方法,则会先执行__sleep方法。该方法可以清除对象并返回一个该对象中所有变量的数组。使用__sleep方法的目的是关闭对象可能具有的数据库连接等类似的善后工作。
unserialize()函数可以重新还原一个被serialize()函数序列化的对象,__wakeup方法则是恢复在序列化中可能丢失的数据库连接及相关工作。
【例14.21】本例首先声明一个类SportObject,类中有两个方法,即__sleep和__wakeup。实例化对象$myBook,使用serialize()函数将对象序列化为一个字串$i,最后再使用unserialize()函数将字串$i还原为一个新对象。实例代码如下:(实例位置:光盘TMsl1421)
<?php /* 创建类SportObject */ class SportObject{ private$type='DIY'; //声明私有变量$type,初值为DIY public function getType(){ //声明getType方法,用来调用私有变量$type return$this->type; //返回变量值 } public function__sleep(){ //声明魔术方法__sleep echo ’使用serialize()函数将对象保存起来,可以存放到文本文件、数据库等地方<br>'; return array('type'); } public function__wakeup(){ //声明魔术方法__wakeup echo ’当需要该数据时,使用unserialize()函数对已序列化的字符串进行操作,将其转换回对象<br>'; } } $myBook=new SportObject(); //实例化对象$myBook $i=serialize($myBook); //序列化对象 echo’序列化后的字符串:'.$i.'<br>'; //输出字串$i $reBook=unserialize($i); //将字串$i重新转换为对象$reBook echo’还原后的成员变量:'.$reBook->getType(); //调用新对象$reBook的getType方法 ?>
运行结果如图14.12所示。
图14.12 __sleep和__wakeup方法
4.__toString方法
魔术方法__toString的作用是:当使用echo或print输出对象时,将对象转化为字符串。
【例14.22】本例输出类SportObject的对象$myComputer,输出的内容为__toString方法返回的内容。实例代码如下:(实例位置:光盘TMsl1422)
<?php class SportObject{ //类SportObject private$type='DIY'; //声明私有变量$type public function__toString(){ //声明__toString方法 return$this->type; //方法返回私有变量$type的值 } } $myComputer=new SportObject(); //实例化对象$myComputer echo ’对象$myComputer的值为:'; echo$myComputer; //输出对象$myComputer ?>
结果为:对象$myComputer的值为:DIY
注意
(1)如果没有__toString方法,直接输出对象将会发生致命错误(fatal error)。
(2)输出对象时应注意,echo或print后面直接跟要输出的对象,中间不要加多余的字符,否则__toString方法不会被执行,如“echo ’字串’.$myComputer”“echo ' '.$myComputer”等都不可以,一定要注意。
5.__autoload方法
将一个独立、完整的类保存到一个PHP页中,并且文件名和类名保持一致,这是每个开发人员都需要养成的良好习惯。这样,在下次重复使用某个类时即能很轻易地找到它。但还有一个问题是让开发人员头疼不已的,如果要在一个页面中引进很多的类,需要使用include_once()函数或require_once()函数一个个地引入。
PHP 5解决了这个问题,__autoload方法可以自动实例化需要使用的类。当程序要用到一个类,但该类还没有被实例化时,PHP 5将使用__autoload方法,在指定的路径下自动查找和该类名称相同的文件。如果找到,程序则继续执行;否则,报告错误。
【例14.23】本例首先创建一个类文件SportObject.class.php,该文件包含类SportObject。再创建index.php文件,在文件中先创建__autoload方法,手动实现查找的功能,如果查找成功,则使用require_once()函数将文件动态引入。index.php文件的最后实例化对象参见输出结果。(实例位置:光盘TMsl1423)
类文件SportObject.class.php的代码如下:
<?php class SportObject{ //声明类SportObject private$cont; //声明私有变量$cont public function__construct($cont){ //创建构造方法 $this -> cont = $cont; } public function__toString(){ //创建__toString方法 return $this -> cont; } } ?>
index.php文件的代码如下:
<?php function__autoload($class_name){ //创建__autoload方法 $class_path=$class_name.'.class.php'; //类文件路径 if(file_exists($class_path)){ //判断类文件是否存在 include_once($class_path); //动态包含类文件 }else echo ’类路径错误。'; } $myBook=new SportObject("江山代有人才出 各领风骚数百年");//实例化对象 echo$myBook; //输出类内容 ?>
结果为:江山代有人才出 各领风骚数百年
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。