深入PHP面向对象,模式与实践 第4章:高级特性
首先,这个博客不是教学的,而是我自己用来回顾的,其中可能直接放实例代码的比较多,你能看懂其中的重点,那么就说明你掌握了这个知识点,但是如果你没有看懂,那么这里也不会有对应的解释,自己百度去。
4.1 静态方法与属性
class IndexClass{
static public $num=0;
static public function addNum(){
self::$num++;
print self::$num."\n";
}
}
IndexClass::addNum();
IndexClass::addNum();
输出结果:
1
2
注意点:
static
的值不是不可修改的,那是常量,静态方法只是说明他属于类,而不是类实例化后的对象。static
的值在整个类的生命周期中都是公用的,在第一次运行IndexClass::addNum();
,到第二次,它的值一直有效,而不是从0开始。- 在静态方法中调用静态值时,采用的是
self
,而且是::
,这里与self
对应的是parent
,而与::
对应的则是->
,一个是因为IndexClass
没有超类,第二个则是因为静态属性属于类,不属于对象,所以使用::
,所以外部调用静态方法时,也是使用::
。 - 静态属性或者方法与可见性(public private protected)不是一个量级的概念,所以他们也可以设置可见性。
4.2 常量
class IndexClass{
const KEY="password";
const PWD_LENGTH=10;
static public function getContent(){
print self::KEY."\n";
print self::PWD_LENGTH;
}
}
print IndexClass::KEY."\n";
print IndexClass::PWD_LENGTH."\n";
IndexClass::getContent();
输出结果:
password
10
password
10
注意点:
- 常量一般命名方式就是全大写字母。
- 常量虽然是变量,但是不需要使用
$
符号。 - 内部或者外部获取常量的方式与获取静态属性的方式类似。
- 常量不可以修改。
- 常量只能包含基本的数据类型。
4.3 抽象类
<?php
// 定义一个基础的类:商品类
class ShopProduct{
public $price;
public function __construct($price=0)
{
$this->price=$price;
}
}
// 定义一个抽象类,其中有抽象方法:write,打印商品价格
abstract class ShopProductWriter{
protected $discount;
// 定义抽象函数
abstract public function write();
public function setDiscount($discount){
$this->discount=$discount;
}
public function getDiscount(){
return $this->discount;
}
// 这里再定义一个静态方法
static public function test(){
return true;
}
}
// 设置一个类,用来打印Cd类商品的信息
class CdProductorWriter extends ShopProductWriter{
protected $shopProductInstance;
public function __construct(ShopProduct $shopProduct)
{
$this->shopProductInstance=$shopProduct;
}
public function write()
{
parent::setDiscount(10);
print $this->shopProductInstance->price-parent::getDiscount()."\n";
}
}
// 再设置一个类,用来打印书的信息
class BookProductWriter extends ShopProductWriter{
protected $shopProductInstance;
public function __construct(ShopProduct $shopProduct)
{
$this->shopProductInstance=$shopProduct;
}
public function write()
{
parent::setDiscount(50);
print $this->shopProductInstance->price-parent::getDiscount()."\n";
}
}
// 实例化商品类时,需要传入对应的价格
$book=new ShopProduct(100);
$cd=new ShopProduct(200);
// 实例化打印类时,需要传入对应打印的商品的对象
$cdProductWriter=new CdProductorWriter($cd);
// 因为打印类都继承自抽象类:ShopProductWriter,所以可以保证其的:write方法一定存在
$cdProductWriter->write();
$bookProductWriter=new BookProductWriter($book);
$bookProductWriter->write();
// 尝试调用从抽象类中继承下来的静态方法
print CdProductorWriter::test()?"可以调用抽象类中的静态方法与属性":"不可以调用抽象类中的静态方法与属性";
输出结果:
190
50
可以调用抽象类中的静态方法与属性
注意点:
- 抽象类可以定义除了抽象方法之外的值,也可以在其中设置对应的属性和方法。
- 抽象类只能被用来继承,不能被用来实例。
- 所谓抽象类比起一般的类只有以下特殊点:
- 在申明前加上了
abstract
。 - 在其中一般至少定义一个
abstract
函数,而函数的可见性则是随意的,但是继承该抽象类的子类实现该抽线函数时,可见性不能比抽象类低,比如抽象类定义的是protected
,那么字类就不能是private
。 - 抽象方法中定义多少参数,覆写时就要传入多少参数,只能一致,不能多也不能少。如果设置了默认值,可以修改默认值。编译器只看数量,不看具体的值
- 在申明前加上了
4.4 接口
<?php
// 定义接口
interface Chargeable{
// 只能定义函数名,而且函数的可见性只能为public
// 或者可以忽略 public,因为如果直接写 function getPrice() 则该方法默认为public
public function getPrice();
public function getPriceInfo();
// 也可以定义静态函数
static public function showDiscount();
// 可以定义常量
const NUM=10;
// 并且在实现类中可以调用该常量
static public function getNUM();
}
// 接口就不叫 继承 了,而叫做 实现,PHP只能继承一个类,但是可以实现多个接口
class ShopProduct implements Chargeable{
protected $price;
public function __construct($price){
$this->price=$price;
}
public function getPrice(){
return $this->price;
}
public function getPriceInfo(){
print "Price:".$this->price." $\n";
}
// 必须实现所有函数,而且可见性都必须是 public
static public function showDiscount(){
print "There is no discount\n";
}
// 获取该常量
static public function getNUM(){
// 注意这里不要跟继承混起来!!!parent::NUM,这里不是继承,而是实现,所以这么写是错误的
return self::NUM;
}
}
$cd=new ShopProduct(100);
print $cd->getPrice()."\n";
$cd->getPriceInfo();
ShopProduct::showDiscount();
ShopProduct::showDiscount();
输出结果
100
Price:100
There is no discount
10
注意点:
- 接口中只能定义函数,不能定义参数,静态的非静态的都不行,可以定义常量,在实现的类中可以调用。
- 实现该接口时,就必须实现该接口中所有方法,静态的非静态的都要,而且不能修改其可见性,简单来说就是要覆写接口中所有方法。
- 接口中只能定义可见性为
public
的函数。 - 实现多个接口时,两个接口间用
,
(逗号)隔开。 - 关于函数中参数的设定与抽象类是一致的。
4.5 延迟静态绑定:static关键字
<?php
abstract class ParentClass{
public static function create(){
return new static();
}
}
class User extends ParentClass{
}
class Animal extends ParentClass{
}
print_r(User::create());
print_r(Animal::create());
这里有一遍博客讲的不错的:static 延迟静态绑定。
这里的重点就在于ParentClass
中的一段代码:
return new static();
与之对应的一种写法是:
return new self();
self
与static
有什么区别呢?self
返回的是ParentClass
,而static
返回的则是继承了ParentClass
的类,简单来说就是在父类中调用了子类。
4.6 错误处理
<?php
// 根据自己需要定义两个特殊的错误处理类
class FileNameEmpty extends Exception{
const CODE=1001;
const MESSAGE="文件名不能为空";
public function __construct($message = "", $code = 0, Throwable $previous = null){
parent::__construct(self::MESSAGE,self::CODE, $previous);
}
}
class FilePathWrong extends Exception{
const CODE=1002;
const MESSAGE="文件路径错误";
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct(self::MESSAGE,self::CODE, $previous);
}
}
// 在这个类中根据情况抛出异常
class GetFile{
public function getFilePath($fileName){
if($fileName==""){
throw new FileNameEmpty();
}
$fileContent=$this->getFileContentInfo($fileName);
if($fileContent==""){
throw new FilePathWrong();
}
return $fileContent;
}
public function getFileContentInfo($fileName){
return ($fileName=="root")?"":"Hello World !";
}
}
// 上一个类中抛出异常,在这个类中进行获取
class FileManager extends GetFile{
public function getFileContent($fileName=''){
try{
$fileContent=$this->getFilePath($fileName);
return $fileContent;
}
// 下面就是根据抛出的异常,制定对应的错误处理方式
catch (FileNameEmpty $e){
print $e->getMessage()."\n";
}
catch (FilePathWrong $e){
print $e->getMessage()."\n";
}
catch (Exception $e){
print $e->getMessage()."\n";
}
}
}
$fileManager=new FileManager();
$fileManager->getFileContent();
$fileManager->getFileContent("root");
print $fileManager->getFileContent("right filePath");
输出:
文件名不能为空
文件路径错误
Hello World !
注意点:
- 可以根据自己需要继承异常类(
Exception
),再来根据对应的异常制定对应的处理方式 - 抛出异常后,下面的代码将不再执行
- 首先对应的函数中有抛出异常的代码
throw new Exception
,在外部才可以使用try catch
获取异常 - 异常类可以抓住多个,但是上个获取后(
catch
),就不会去获取下一个,所以一定要把catch(Exception $e)
放到最后 - 实例化异常类后,需要传入两个参数
message
,code
,这两个只是为了在获取异常时可以获取对应的异常的提示信息,而与Exception
本身无关。
4.7 Final 类和方法
// 定义一个类为 final 类
final class EndClass{
}
class OutPutClass{
// 定义一个方法为 final 方法
final public function getDiscount(){
return 10;
}
}
注意点:
final
类不能被继承,除此之外,与一般的类使用方法一致,可以被实例化,可以设置静态,非静态的函数与属性。final
方法不能被覆写。
4.8 使用拦截器
<?php
class PersonWriter{
// 5
public function writeName(Person $person){
print $person->getName()."\n";
}
public function writeAge(Person $person){
print $person->getAge()."\n";
}
}
class Person{
private $writer;
// 2
public function __construct(PersonWriter $writer)
{
$this->writer=$writer;
}
// 4
public function __call($name, $arguments)
{
if(method_exists($this->writer,$name)){
return $this->writer->$name($this);
}
}
// 6
public function getName(){
return "Name";
}
public function getAge(){
return "Age";
}
}
// 1
$person=new Person(new PersonWriter());
// 3
$person->writeName();
输出
Name
注意点:
- 仔细看上面的类的调用,其中的调用很复杂,我在其中把调用顺序用数字标出来了
- 重点的一段代码就是:
return $this->writer->$name($this);
,这里就像是在用运行时,用字符串临时组成代码的运行程序一样。
4.9 析构方法
class Example{
public function __construct(){
print "该类被实例化\n";
}
public function __destruct(){
print "该类占用的资源将被释放\n"
}
}
4.10 使用__clone()复制对象
这里有一些别的笔记,我需要先记一下:
<?php
class Example{
public $num=10;
public function addNum(){
$this->num++;
return $this->num;
}
}
$firstNum=new Example();
$secondNum=$firstNum;
print $firstNum->addNum()."\n";
print $secondNum->addNum()."\n";
输出
11
12
注意点:
- PHP 4以及之前的版本:
$second
是一个新的对象。 - PHP 5以及之后的版本:
$first
,$second
是针对同一个对象的两个调用。 - 所以
$secondNum
运行addNum
时,是从11开始的,而不是10,因为$firstNum
已经运行过一次了。
这里再记录一个注意点:== 判断两个值是否相等,===判断两个值是否相等,且是否都是对象,但是无法判断这两个值是两个相同的对象,还是针对一个对象的两次引用。
所以在PHP 5之后,要想复制一个对象,可以使用clone
函数
<?php
class Example{
public $num=10;
public function addNum(){
$this->num++;
return $this->num;
}
public function __clone()
{
$this->num=10;
}
}
$firstNum=new Example();
print $firstNum->addNum()."\n";
$secondNum=clone $firstNum;
print $secondNum->addNum()."\n";
注意点:
__clone
函数只有在对象被clone
函数执行时才会运行,像一般的$person2=$person;
这样的方式是不会处罚该函数的。clone
函数运行时,会把对象复制出一个新的给变量,但是如果其中存在的某个属性是对一个对象的调用,则不会创建对应对象的副本,而只是单纯的引用。
<?php
class CountNum{
private $num=0;
public function addNum(){
$this->num++;
}
public function getNum(){
return $this->num;
}
}
class Example{
public $num=10;
public $countNum;
public function __construct(CountNum $countNum)
{
$this->countNum=$countNum;
}
public function __clone()
{
$this->countNum=new CountNum();
}
public function addCountNum(){
$this->countNum->addNum();
}
public function getCountNum(){
return $this->countNum->getNum();
}
}
$firstNum=new Example(new CountNum());
$secondNum=clone $firstNum;
$firstNum->addCountNum();
print $secondNum->getCountNum();
输出
0
注意点:
- 这里的重点就一个
$this->countNum=new CountNum();
,如果没有这段代码,则直接输出1
,因为运行$secondNum=clone $firstNum;
时,$firstNum
中的属性public $countNum;
与$secondNum
中是针对同一个CountNum
的引用,所以$firstNum
的对其的操作会直接影响到$secondNum
上去。但是在__clone
中将$secondNum
的$countNum
指向了一个新对象,所以两者就无关了。
4.11 定义对象的字符串值
<?php
class OutPutString{
public function __toString()
{
return "print/echo 该类时,该方法的返回字符串将被调用";
}
}
$outputString=new OutPutString();
print $outputString;
输出
print/echo 该类时,该方法的返回字符串将被调用
4.12 回调,匿名函数和闭包
在介绍知识点之前,先看几个函数:
- call_user_func
<?php
$functions=function ($param){
print "调用匿名函数,并传入参数:{$param}\n";
};
call_user_func($functions,10);
在这里再介绍一个调用对象中类的方式
<?php
class HandleNum{
public function outputNum($num){
print "The Num is {$num}\n";
}
// 注意,这里不能写成 public function addNum(&$num) 这样,只能传入值,不能传入引用
public function addNum($num){
$num++;
}
}
$num=10;
// 一个对象实例作为第一个参数,需要调用的方法作为第二个参数传入,可以以这样的方式调用对象中的方法来处理数组
call_user_func(array(new HandleNum(),"outputNum"),$num);
call_user_func(array(new HandleNum(),"addNum"),$num);
print $num;
- array_walk
<?php
$array=range(0,10);
$unChangeArray=$array;
// 注意这里调用第一个参数时,采用的是 &$value,传入的不是参数的值,而是参数本身,所以在函数中对参数作出修改后,也能直接修改原始数组的值
$functions=function (&$value,$key){
$value++;
};
array_walk($array,$functions);
print "未修改数据前\n";
print_r($unChangeArray);
print "调用函数修改过数据后\n";
print_r($array);
输出结果
注意点:
- 这里创建了一个匿名函数
$functions=function
,并将其保存在变量$functions
中。
返回一个匿名函数
<?php
$anotherFunction=function ($outsideParam){
return function ($anotherFunctionParams) use ($outsideParam){
print "输出父作用域中传入的函数:{$outsideParam}\n";
print "传入该匿名函数中传入的函数:{$anotherFunctionParams}\n";
};
};
$newFunction=$anotherFunction("创建函数时传入的参数");
$newFunction("创建的函数被调用时传入的参数");
这里就是想说明两点:
- PHP运行时,感觉可以将接下来要执行的代码用字符串拼接起来一样。
- 所谓的函数,也可以作为一个变量来存储,来返回。
- 在返回函数时,要将父作用域中的参数不是作为参数传入返回的函数中的方法就是使用
use
。