Introduction
阅读《PHP核心技术与最佳实践》对PHP面向对象相关知识的总结、整理与记录。
面向对象 OOP
面向对象程序设计(Object-Oriented Programming,OOP)是一种程序设计范性,同时也是一种程序开发方法。它将对象作为程序的基本单元,将程序和数据封装其中,以提代码的重用性、灵活性和可扩展性。
面向对象的核心思想是对象、封装、可重用和可扩展性。
面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想(面向过程)刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。
通常,OOP被理解为一种将程序分解为封装数据及相关操作的模块而进行的编程方式。
类是对象的抽象组织,对象是类的具体存在。
PHP对象的本质
先看PHP5中变量的定义:
#zend/zend.h
typedef union _zvalue_value {
long lval; //整形
double dval; //浮点数
struct {
char *val;
int len;
} str; //字符串
HashTable *ht; //数组
zend_object_value obj; //对象
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
从源码可知php对象的类型是zend_object_value,接下来看它是如何实现的:
#zend/zend.h
typedef struct _zend_object {
zend_class_entry *ce; //类指针,指向对象对应的类
HashTable *properties; //存放对象的属性表
HashTable *guards;
/* protects from __get/__set ... recursion */
} zend_object;
PHP对象:
- 属性数组(哈希表)
- 类指针
- 类属性
- 静态属性
- 类常量
- 标准方法
- 魔术方法
- 自定义方法
对象也是一种普通的变量,不同的是其携带了对象的属性表和类的入口指针。
于是可以总结PHP中对象和类的概念以及二者之间的关系:
- 类是定义一系列属性和操作的模版,而对象则把属性进行具体化,然后交给类处理
- 对象就是数据,本身不包含方法。但是对象有一个“指针”指向这个类,类里包含方法
类常量
可以把在类中始终保持不变的值定义为常量。可以使用self(内部)或者类名/对象(外部)加::
来访问类常量。
class MyClass {
const constant = 'constant value';
function showConstant() {
echo self::constant . "\n";
}
}
var_dump(MyClass::constant);
$obj = new MyClass();
var_dump($obj::constant);
魔术方法
PHP魔术方法是以两个下划线”__“开头,具有特殊作用的方法。
__construct() // 构造方法
__destruct() // 析构方法
PHP中的“重载”是指动态地创建类属性和方法:
属性“重载”,当访问/设置的属性未定义/不可见时,PHP会自动调用对应的魔术方法
__get($name)
__set($name, $value)
方法“重载”,当访问/设置的方法未定义/不可见时,PHP会自动调用对应的魔术方法,从而可以实现方法的动态创建
__call($func_name, $parameters) __callStatic($func_name, $parameters)
__toString()
PHP面向对象特性
通常,OOP被理解为一种将程序分解为封装数据及相关操作的模块而进行的编程方式。
- 封装(Encapsulation),通过类来组织代码和限定数据访问权限将类与其外部世界划清界限,内外隔离来形成代码模块,让程序的结构更加清晰可理解
- 继承(Inheritance),对类进行复用和扩展
- 多态(Polymorphism),是指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。它真正的意义在于:实际开发中,只要关心一个接口或基类的编程,而不必关心一个对象所属的具体类,同一类型(基类/接口),不同结果
代码复用分为继承和组合,继承是一种“是、像”的关系,而组合是一种“需要”的关系。
耦合是软件结构内不同模块之间互相连接程度的度量,解耦就是要解除模块与模块间的依赖。
PHP中的抽象类与接口:
- 接口,多继承,只能声明 public 的方法,可以声明常量变量但不推荐这么做。接口是类与类之间的“协议”,定义了类的规范,为抽象而生。但是PHP的接口有两个不足:一是没有契约限制,使用时不做检查,二是缺少足够的内部接口。
- 抽象类,单继承,可以声明各种类型成员变量及类常量,也可以定义非抽象方法。
抽象类一般用于紧密相关的事物,而接口则用于事物的相同行为。抽象类核心是类及其行为,接口的核心是相同行为。如果必须从多个来源继承行为,就使用接口。
反射 Reflecton
反射,直观的理解就是根据到达地找到出发地和来源,探测类/对象的内部结构。可用于对文件里的类进行扫描来生产文档和拦截和修改方法的运行实现动态代理。
异常(Exception)与错误处理(Error Handle)
PHP异常机制有不足,大部分情况无法自动抛出异常,一般只有手动抛出后才能捕获异常,但这也不影响异常机制的使用。下面三种情况会用到异常处理机制。
- 对程序的悲观预测,有无法预测/不可避免的情况发生
- 程序的需要和对业务的关注,业务很重要,及早处理异常
- 语言级别的健壮性要求,作为一种程序的补救措施及时catch和处理异常
PHP错误就是会使脚本运行不正常的情况。大致的错误级别:
- deprecated
- notice
- warning
- fatal error
error_reporting — 设置应该报告何种 PHP 错误 php.ini // 显示错误 display_errors = Off // 记录错误日志 log_errors = On error_log = /var/log/php_errors.log
error_reporting(0); // 关闭所有PHP错误报告
error_reporting(-1); // 报告所有 PHP 错误
error_reporting(E_ALL);
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
error_reporting(E_ALL ^ E_NOTICE); // 除了 E_NOTICE,报告其他所有错误
接管错误和异常处理,获得更多灵活性: set_error_handler — 设置用户自定义的错误处理函数 restore_error_handler — 还原之前的错误处理函数
以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。
set_exception_handler — 设置用户自定义的异常处理函数 restore_exception_handler — 恢复之前定义过的异常处理函数
mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )
bool restore_error_handler ( void )
callable set_exception_handler ( callable $exception_handler )
bool restore_exception_handler ( void )
register_shutdown_function — 注册一个会在php中止时执行的函数 // 注册一个 callback ,它会在脚本执行完成或者 exit() 后被调用。
面向对象设计原则
设计模式主要探讨的是类与类之间的关系。
面相对象五大设计原则:
- 单一职责原则
- 接口隔离原则
- 开放-封闭原则
- 替换原则
- 依赖倒置原则
单一职责原则(Single Responsibility Principle),它有两个含义:一个是避免相同的职责分散到不同的类中,另一个是避免一个类承担太多职责。可以减少耦合提高复用,是最简单也是最难做好的职责之一。
- 工厂模式,根据不同参数,生成不同的实例化对象。
接口隔离原则(Interface Segregation Principle),一个类对另一个类的依赖性应该是建立在最小的接口上的,接口的内容应该服务于一个近似不可分割的小模块。“接口隔离”其实就是定制化服务设计的原则,服务(接口)尽量小、自成一体与外部隔离,使用多重继承实现对不同接口的组合,从而对外提供组合功能——达到“按需提供服务”。
开放-封闭原则(Open-Close Principle),开放:模块的行为必须是开放的、支持扩展的,而不是僵化的。封闭:在对模块功能进行扩展时不应该影响或大规模影响已有的程序模块。概括一下就是:一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。
实现开放-封闭原则的核心就是对抽象编程,而不对具体编程,因为抽象稳定。例如,让类依赖于固定的抽象类,这样的修改就是封闭的;通过面相对象的继承和多态机制,可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。具体实践中应该:
- 在设计方面充分应用“抽象”和“封装”思想,抽象出不变的部分将其封装成模块。
- 在系统功能编程实现方面应用依赖抽象(抽象类/接口)的编程。
替换原则(Liskov Substitution Principle),子类型必须能够替换掉它们的父类型、并出现在父类能够出现的任何地方。简单来说就是:子类必须能够替换成它们的基类。
如何遵守该原则呢?
- 父类的方法都要在子类中实现或者重写,并且派生类只实现其抽象类中声明的方法,而不应当给出多余的方法定义或者实现。
- 在客户端程序中只应该使用父类对象而不应当直接使用子类对象,这样可以实现运行期绑定(动态多态)。
依赖倒置原则(Dependence Inversion Principle),简单讲就是将依赖关系倒置为依赖接口,依赖倒置的核心是解耦,具体概念如下:
- 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象;例如:父类不能依赖子类,它们都要依赖抽象类。
- 抽象不能依赖于具体,具体应该要依赖于抽象。
其实,面相过程也好,面向对象也好,目的只有两个:
- 功能实现
- 代码维护和扩展
单例模式 Singleton
- 只能有一个实例
- 必须自行创建这个实例
- 必须给其他对象提供这一实例
PHP单例模式的优缺点:虽然PHP每次执行完页面都是会从内存中清理掉所有的资源。因而PHP中的单例实际每次运行都是需要重新实例化的,但PHP一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为,使用单例模式可以避免(在单次请求中)大量的new操作。因为每一次new操作都会消耗系统和内存的资源。