一、面向对象的思想和概念
面向对象的理解
- 面向对象和面向过程的对比
面向对象编程语言(C++、Java、PHP)和面向过程编程语言(C)的区别。由于只熟悉面向对象语言,只是了解面向对象语言的封装、继承、多态的特性,并不是很熟悉面向过程语言(C)可能没办法更好的体会面向对象的这几个特性。
面向对象只是编程语言的特定,代码能不能体现面向对象的特点,主要是靠代码的设计和编码。
- 类和对象的关系
类是属性和方法的集合,而对象则是属性的集合。
打印对象时只有属性,没有方法。
类的区分应该是靠属性来区分,即不同的类应该是由不同的属性构成。
对象是类的实例化结果,不同的对象有不同的属性,但是共享了类中的方法。
- 对象的本质
对象在PHP底层是如何保存的?
对象在PHP底层也是一种变量,和字符串,数组类似,也可以理解为是特殊的变量。都是以结构体的方式来保存的。
#zend/zend_types.h
typedef union_zend_value{
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj; //用于对象
zend_resource *res;
zend_reference *ref;
zend ast_ref *ast; //用于常量表达式
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct{
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
下图是PHP源码里对变量的定义,我们看到对象也是其中的一种。zend_object
我们看对象在PHP源码里是以何种结构体来定义的:
#zend/zend_types.h
struct_zend_object{
zend_refcounted_h gc;
uint32_t handle; // TODO:may be removed???
zend_class_entry *ce; //这里就是入口
const zend object_handlers *handlers;
HashTable *properties;
zval properties_table[1];
}
ce是存储该对象的类结构,在对象初始化保存了类的入口,相当于类指针的作用。
- 对象与数组
既然对象和数组在底层是一样的结构,那我们就可以来比较下这两者有什么区别。
从PHP代码来看,两者对我们而言更多的可能是取值的方式的区别,下面我们通过序列化来比较下。
//数组序列化
$student_arr = array('name'=>'Tom','gender'=>'male');
echo serialize($student_arr);
echo "\n";
//对象序列化
class person{
public $name;
public $gender;
public function say(){
echo $this->name," is ",$this->gender;
echo "\n";
}
}
$student = new person();
$student->name = 'Tom';
$student->gender = 'male';
$student->say();
echo serialize($student);
echo "\n";
输出结果
a:2:{s:4:"name";s:3:"Tom";s:6:"gender";s:4:"male";}
Tom is male
O:6:"person":2:{s:4:"name";s:3:"Tom";s:6:"gender";s:4:"male";}
我们看到,数组和对象序列化后的结果基本相同,唯一的区别就是对象前会有类的名称,这个名称可以对应到源码结构中的ce指针,就是类的入口。就是说,通过这个指针可以找到对象是所属哪个类的。
魔术方法
- __set
给一个不存在的属性赋值时被调用 - __get
获取一个不存在的属性值,和获取无权限的属性时(例如类外部调用private和protected的属性)被调用 - __call
调用不存在的方法时触发 - __callStatic
调用不存在的静态方法时触发 - __toString
echo一个对象时触发 - __debugInfo
打印所需调试信息
命名空间和自动加载
自动加载函数 spl_autoload_register
继承和多态
类的继承与组合
- 继承破坏封装性
- 继承是紧耦合的
- 继承扩展复杂
组合相比于继承,更易于扩展,更灵活,耦合度更低,但是组合的代码量更多。
组合可以理解为include/require其他类
PHP中如果既要组合的灵活,又要继承的简洁,那就需要用到trait
这个语法
详细介绍点这里
多态
多态更多的是针对强类型语言来说的。
多态的定义:
同一类的对象收到相同消息时,会得到不同的结果
简单归纳就是同一类型,不同结果
重点就在同一类型上,对于弱类型语言,所有的对象都可以理解为同一种类型,所以,从某种意义上说,弱类型语言本身就是多态,但是在强类型语言中,这不是多态。
但是PHP中有一个接口
的概念,通过接口
可以实现符合同一类型,不同结果
的多态。
working();
}
$a = new teacher;
$b = new coder;
$c = new farmer;
doprint($a);
doprint($b);
doprint($c);
以上代码输出结果
teaching
coding
PHP Catchable fatal error: Argument 1 passed to doprint() must implement interface employee, instance of farmer given, called in /home/hupengchen/php_study/1-10.php on line 33 and defined in /home/hupengchen/php_study/1-10.php on line 24
我们可以看到当限定doprint函数的参数是一个接口类型变量时,就能很好的理解多态。farmer类不是接口类型,所以会报错。
面向接口编程
PHP中接口并不是很严格,PHP里只关心是否实现接口里的方法,而并不关心接口语义是否正确。
如下代码所示:
#interface.php
fly(); //mobile接口是没有这个方法的
}
}
$obj = new machine();
$obj->demo(new plain());
$obj->demo(new car());
输出结果
this is plain fly!
PHP Fatal error: Call to undefined method car::fly() in /home/hupengchen/php_study/interface.php on line 23
通过上面代码,我们可以看到在PHP中,可以通过接口类型去实现一个接口中并不存在的方法。而在Java中,当你定义了一个接口类型的参数,但又要实现一个接口中不存在的方法时,会报错,因为这并不符合接口的规范。
所以,我们可以认为接口是一套规范,当我们在PHP中用的时候,也尽量是在需要定义一套规范的场景中使用。
PHP内置的一个Iterator迭代器接口
,通过这个接口可以使对象使用foreach结构。
反射
反射就是可以通过对象找到类(包括类的属性,方法,文件名等一切内容)
用法:
使用ReflectionObject
来实现反射
$reflect = new ReflectionObject($student);
//获取对象属性列表
$props = $reflect->getProperties();
foreach($props as $prop){
$prop->getName();
}
//获取对象方法列表
$m = $reflect->getMethods();
foreach($m as $prop){
$prop->getName();
}
异常和错误
其他语言中,异常和错误是分开处理的,但是在PHP中遇到自身任何错误都是触发一个错误。
PHP中需要throw
手动抛出异常,才能捕获异常。
PHP的错误机制
php7对异常机制的改进:
php7实现了一个全局的Throwable
接口,原来的Exception
和部分error
实现了这个接口。以接口的方式定义了异常的继承结构。
我们以上一节interface.php中的代码来验证下PHP7中和PHP5中对Throwable
的改进。
#测试Exception
try{
$obj->demo(new car());
}catch(Exception $e){
echo 'catch exception!'.PHP_EOL;
echo $e->getMessage().PHP_EOL;
}
//php5运行
[root@localhost php_study]# php interface.php
this is plain fly!
PHP Fatal error: Call to undefined method car::fly() in /home/hupengchen/php_study/interface.php on line 23
//php7运行
[root@localhost php_study]# php72 interface.php
this is plain fly!
PHP Fatal error: Uncaught Error: Call to undefined method car::fly() in /home/hupengchen/php_study/interface.php:23
Stack trace:
#0 /home/hupengchen/php_study/interface.php(30): machine->demo(Object(car))
#1 {main}
thrown in /home/hupengchen/php_study/interface.php on line 23
Fatal error: Uncaught Error: Call to undefined method car::fly() in /home/hupengchen/php_study/interface.php:23
Stack trace:
#0 /home/hupengchen/php_study/interface.php(30): machine->demo(Object(car))
#1 {main}
thrown in /home/hupengchen/php_study/interface.php on line 23
我们看到php7虽然对于PHP5多了异常的提示,但是也是作为错误抛出。下面我们来看Throwable
#测试Throwable
try{
$obj->demo(new car());
}catch(Throwable $e){
echo 'catch exception!'.PHP_EOL;
echo $e->getMessage().PHP_EOL;
}
//php5运行
[root@localhost php_study]# php interface.php
this is plain fly!
PHP Fatal error: Call to undefined method car::fly() in /home/hupengchen/php_study/interface.php on line 23
//php7运行
[root@localhost php_study]# php72 interface.php
this is plain fly!
catch exception!
Call to undefined method car::fly()
我们看到PHP7中用Throwable
已经可以将异常捕获到了。
小结
我们看到PHP虽然是面向对象语言,但是毕竟是弱类型的动态脚本语言,相对于Java和C++等强类型的语言,在面向对象的特性方面(命名空间,多态,接口,异常处理等),并不是很严谨。但是这正是PHP的特点——灵活。使得PHP成为全球web应用使用最多的编程语言。