面向对象
一. 为什么使用面向对象
面向过程思想
我们在生活中,经常使用面向过程的思想来解决问题,比如一下两个场景:
面向对象思想
同样是解决上述两个场景中的问题,我们如果换成面向对象思想来解决,可以用以下两个方式来办:
面向过程思想的局限性
1, 使用面向过程思想开发,代码冗余率高,重用性低;
2, 使用面向过程思想开发,如果中途需求发生改变,那么代码的改动量将会相当庞大。
例如,我们以前做的新闻管理系统中,每个页面都用到了 连库基本操作(三步走),新闻管理系统只有4个页面,比较少,如果web项目庞大,我们每一个页面都去重复的写 连库基本操作 导致的后果将是 程序开发人员的工作量将会相当的大。
所以,我们需要使用更加高效的方式来弥补面向过程思想的局限性。这个高效的方式就是面向对象!
二. 什么是面向对象
面向对象的三个英文单词缩写:OOP(Object Oriented Programming)
概念:以面向对象思想为指导的一种编程方式。
通俗的理解面向对象为:找个对象帮我做事!
三. PHP中的类与对象
1.类
概念
具有 相同属性 和 方法 的集合我们统称为一个类。
语法
通过关键字class来定义一个类:class 类名{}
类成员
包括 成员属性 和 成员方法,定义方式如下图:
2.对象
在PHP中,我们需要根据一个已有的类来创建(实例化)对象,
创建对象的语法:new 类名;
Code1.php
为成员属性赋值
我们通过对象调用成员属性的形式为:保存对象的变量->属性名
注意:上面的结构中属性名不包括”$”。
Code2.php
调用成员方法
我们需要通过对象来调用成员方法,调用的形式:保存对象的变量->方法名();
创建对象的原理
从原理图分析中,我们了解到:
1, 每一次实例化出一个新的对象,都会在内存中开辟一个新的内存空间;
2, 通过对象操作成员属性和方法其实是操作自己内存空间中的成员属性和成员方法;
$this
Code4.php
我们通过分析原理图,了解到:
1,$this代表当前这个对象(空间),比如上图,$zhangsan对象中的$this代表的就是#1对象空间,$lisi对象中的$this代表的就是#2对象空间。
$this:哪个对象调用,就代表那个对象(空间)。
对象的比较
从效果上看,两个全新的对象,如果使用”==”做比较,将会返回true,如果使用”===”做比较,将会返回false.
原因:如果使用”==”做比较,比较的只是内存空间中的内容;如果使用”===”做比较,不同的空间也会进行比较,所以从效果上有如上图之体现。
但是,如果我们将两个全新对象中的某一个对象,改变其中属性值,那么,“==”比较也将会是返回false,因为,内存空间中的内容已经不一样了。
3.类的静态成员
分类
包含 静态成员属性 和 静态成员方法
Code6.php
我们需要在定义非静态成员属性和非静态成员方法的基础上再加上一个关键字static来定义静态的成员属性和静态的成员方法,如上图所示。
静态成员的访问(类外)
我们在类的外部,都是通过类名来访问静态成员的。
格式:类名::静态成员
注意:我们并不是通过对象来访问静态成员的。
TIPS:”::”这个符号叫范围解析操作符。
静态成员的特点
通过分析原理图,我们了解到:
1, 静态成员将会保存在静态空间,一个类只有一个静态空间;
2, 我们没有办法通过一个对象来调用静态成员;
3, 我们在类的外部只能通过类名来调用静态成员;
类中访问静态成员
1, 通过类名的方式访问静态成员
下图是在类中的静态方法中访问静态成员,code8.php
下图是类中非静态成员方法中访问静态成员,code9.php
2,通过self关键字来访问
我们可以在类的内部用self关键字代替类名来访问静态成员,
self的意思表示 当前类的类名。
Code10.php
4.类常量
定义
我们可以在类的内部通过const关键定义一个类常量,
访问类常量
同样,我们也需要区分在类的外部和在类的内部访问类常量,在类的外部,我们需要通过 类名::类常量名,在类内部访问类常量我们可以通过类名::类常量名 或者self::类常量名。
5.小总结
我们到目前为止,学习到了类中的成员有:
1, 非静态成员(非静态成员属性和非静态成员方法)
2, 静态成员(静态成员属性和静态成员方法)
3, 类常量
(注意理清思路,无非就是如何定义成员和如何访问成员)
6.构造方法
我们以前,在创建对象后,经常要初始化对象中的成员属性,但是每次都一步一步通过手动方式初始化,非常不便,如下图:
在PHP中,我们可以通过使用 构造方法 来简化上述操作。
构造方法的定义
我们需要在类的内部定义一个名为“__construct”的非静态成员方法,(注意:这个名字是固定不能定义成其他的名字)
Code13.php
构造方法执行的时机
我们发现,构造方法是在实例化一个对象的时候,被自动调用执行了!
Code13.php
执行的时机和特点:
1, 在实例化对象的时候,构造方法将会被PHP自动调用;
2, PHP不负责定义这个构造方法,只负责调用这个构造方法;
老版本的构造方法
老版本构造方法的命名为:与类名一样。如下图:
Code14.php
7.析构方法
析构方法的定义
我们需要在类中定义一个名为“__destruct”非静态方法,(注意:这个名字是固定的)
注意:析构方法中不能指定形参,否则将会报致命的错误。
析构方法执行的时机和特点
1, 当对象被销毁(手动销毁和程序运行结束自动销毁)的时候,PHP会自动调用名为”__destruct”的析构方法;
2, PHP只负责调用析构方法,不负责定义析构方法。
8.对象的传值
我们先来看一下效果:
对象的值传递code17.php
对象的引用传递code17.php
从效果上看,对象的值传递和对象的引用传递效果一样!
对象传值的原理
对象值传递的原理图:
从上图我们了解:对象的值传递的过程实质上就地复制出了一份值空间,但是,两个值空间都指向了同一个对象空间,最终,我们通过lisi对象改变了$name属性,zhangsan对象的$name属性也就被改变了。
对象的引用传递:
从上图我们了解:对象的引用传递的过程实质上是指向了同一个值空间,最终,我们通过lisi对象改变了$name属性,zhangsan对象的$name属性也就被改变了。
所以,我们如果想通过对象的值传递获得一个全新的对象空间,将无法实现。那么,我们想要通过某种方式基于一个已有的对象获得一个全新的对象空间,该怎么办呢?
在PHP中,我们可以通过克隆的方式来实现。
9.对象的克隆
我们可以通过“clone”关键字来实现克隆(复制出一个全新的对象)
语法:clone 保存对象的变量名;
对象克隆的原理
对象的克隆实际上就是,复制出一个全新的对象空间,如下图所示:
__clone魔术方法
从效果上看:
1, 当我们执行clone操作的时候,类中的”__clone”方法将会被PHP自动调用执行;
特点:
1, PHP不负责定义这个魔术方法“__clone”,只负责调用这个魔术方法。
10.案例:封装MYSQL操作类
功能分析
1, 三步走(连库,设置字符集,选择数据库)
2, 新增操作
3, 修改操作
4, 删除操作
5, 查询操作(查询多条,查询一条记录)
6, 销毁操作
程序实现
Code20.php
11.自动加载
在开发web项目中,我们可能需要大量使用到封装好的类,通常情况下,我们会将所有这些类文件在页面的开头就将其引入进来。
这样做虽然方便,但是并不友好,我们在页面中,并不一定所有的类都会使用到,所以,我们的策略应该是,什么时候使用到,就什么时候将这个类(类文件)引入进来。
在PHP中,我们可以通过自动加载来实现我们的期望。
默认的自动加载函数
在PHP中,我们可以定义一个名为”__autoload”的函数,来实现自动加载。
注意:这个函数可以指定一个参数,参数为实例化类的类名。
特点:
1, 默认的自动加载函数需要我们手动的定义出来;
2, 当我们需要用到类的时候,如果已经定义除了自动加载函数,PHP会自动调用这个函数,并且把类的类名作为参数传递给默认的自动加载函数。
自定义的自动加载
自定义自动加载函数
第一步,定义一个自定义的函数
Code4.php
注意:需要指定一个形参,这个形参是使用到的类的类名。
第二步,将这个自定义的函数注册成为自动加载函数
需要通过spl_autoload_register函数来将某个函数注册成为自动加载函数。
Code4.php
最终效果:
Code4.php
自定义自动加载方法
非静态方法
Code5.php
静态方法
注册静态方法为自动加载方法的另一种写法(项目中最常见的写法)
自动加载的特点
1, 如果我们注册了自定义的自动加载函数或方法,那么默认的自动加载函数将失效;
2, 注册自定义的自动加载函数和方法可以注册多个。
3, 如果我们注册了自定义的自动加载,还想使用回默认的自动加载,那么,我们必须将默认的自动加载函数再次注册一次。
四. PHP中类的继承
1. 为什么使用类的继承
我们可以通过类的继承,减少代码的重复定义,提高代码的重用性。
我们要想让鸟类和鱼类在程序中具有动物类中的的属性,就需要分别在鸟类和鱼类中定义出来;书本类和手机类也一样。这样的操作非常繁琐。
2.什么是类的继承
我们说,属于A类的成员同时也属于B类,我们就说B类继承A类。
继承的语法和基本应用
语法:子类名 extends 父类名 {}
3.继承中的几个基本概念
父类:被继承的类。
派生:通过一个已有的类,而产生了一个新的类,这个过程,我们称为 派生。
子类:继承其他类的类。(派生类)
4.继承链
下图青绿色方框区域就是我们的继承链部分。我们通过$b对象查找父类中的$name属性,实际上是沿着继承链向上查找。
5.parent
Parent关键字可以在子类当中代替父类的类名使用。
Code11.php
6.访问限定修饰符
Public 表示公有的类型
Protected 表示受保护的类型
Private 表示私有的类型
访问限定修饰符,可以用来限定 成员属性 和 成员方法。
限定成员属性
通过访问限定修饰符来限定成员属性,如下图:
限定成员方法
通过访问限定修饰符来限定成员方法,注意:如果不写表示默认使用public类型修饰。
访问限定修饰符的意义
Public 表示能够在类的内部,类的外部和类的继承链上都能被直接访问。
在以下的代码例子中,我们所有访问的地方都能够直接访问public类型的成员!
Protected 表示只能够在类的内部和类的继承链上被直接访问。
Code15.php code16.php
Private 表示只能在类的内部被直接访问。
Code13.php code14.php
7.重写
为什么会有重写
1, 有时候,在子类中,我们需要对父类中已有的一些方法进行改进;
2, 有时候,我们也会因为误操作(没有回看父类中存在哪些方法),在子类中定义了一些方法,与父类中的某个方法重名了;(这个是不愿意看到的情况,一般可以通过改变子类中该方法的名字来避免这个错误)
什么是重写
所谓的重写,就是对父类中已有的方法进行重新定义。
构造方法的重写问题
有时候,我们需要在子类中使用构造方法初始化一些参数,如果此时,父类中也存在构造方法,父类中的构造方法将会被子类中的构造方法所重写。
我们可以通过parent关键在,在子类的构造方法中先调用执行一下父类的构造方法,来解决构造方法重写的问题。
8.继承的特点
1,继承只能单继承,不能多继承,但是可以形成一个长串的继承链。
2,只能继承父类中的非私有成员。
五. Final
Final类 和 final方法
Final类
语法:final class 类名{}
Final方法
我们还可以使用final关键字让父类中的成员方法不能被重写。
注意:虽然我们将某个类中的方法进行final申明了,但是这个方法所在的类依然能够被继承;final关键字只控制这个方能不能够被重写。
六. 抽象类
定义
语法:abstract class A{}
抽象类的成员
包括 普通类的成员 和 抽象方法。
Code24.php
注意:抽象方法一定不能包括方法体,否则将报错。
抽象类的特点
1, 抽象类只能被继承,不能被实例化。
Code25.php
Code26.php
2.抽象类如果被普通类所继承,抽象类中的抽象方法必须需要全部被实现,如果有一个没有被实现,都将报错。
3,抽象类可以继承抽象类,被抽象类继承以后,抽象方法可以不被完全实现。
七. 静态延时绑定
概念:表示哪个类调用,就代表那个类。
静态延时绑定是PHP5.3版本之后的改进特性。
我们可以在父类中使用static关键字来代替子类的类名。
静态延时绑定的思路:
在编译的时候,无法确定到底是哪个子类调用该方法(或属性)(因为可以有无限多个子类可以继承该类);只有在程序执行的时候 才能确定究竟是哪个子类调用到了他,static才能绑定哪个子类的类名。
八. PHP中的接口
定义
通过interface关键字来定义一个接口。
语法:interface 接口名 {}
组成成员
包括 接口常量 和 接口抽象方法(不包含abstract)
特点
1,接口可以被普通类所实现,如果普通类实现了接口,则接口中的所有接口抽象方法都要被实现,一个没有被实现都将报错。
Code2.php
2.接口还能够被抽象类所实现,如果被抽象类所实现,那么接口中的接口抽象方法可以不被完全实现。
Code3.php
3.接口可以被多实现。
Code4.php
4.接口中的接口抽象方法只能是public类型。
接口的使用
普通类实现接口的使用
Code6.php
抽象类实现接口的使用
九. PHP中的重载(overload)
其他语言的重载
在其他语言中,我们把方法名相同,但是指定的参数个数不同的情况叫做重载。
PHP中的重载
当访问一个不可访问的成员时,如何进行相应的处理,我们把这个处理过程就叫做PHP中的重载。
在类中,不可访问的成员值的是:非公有的成员 和 不存在的成员。如下图:
PHP中的重载分为:属性的重载 和 方法重载。
属性的重载
1, 当获取一个不可访问的(非静态的)成员属性时;
2, 当修改一个不可访问的(非静态的)成员属性时;
3, 当删除一个不可访问的(非静态的)成员属性时;
4, 当判断一个不可访问的(非静态的)成员属性是否存在时;
第一种(对应1)
当获取一个不可访问的(非静态的)成员属性时;
PHP默认的处理方式是直接报错,如下图:
但是我们可以通过一个 魔术方法__get来对这个处理方式进行自定义。
Code11.php
当我们构建一个__get魔术方法时,就相当于当我们访问不可访问的成员属性var2时,处理权交给了我们自己,而不再是PHP默认的报错处理方式。
第二种(对应2)
当修改一个不可访问的(非静态的)成员属性时;
PHP默认的处理方式是报错,如下图:
但是,我们可以通过构建一个__set魔术方法来对处理方式进行自定义。
Code12.php
当我们构建一个__set魔术方法时,就相当于当我们修改不可访问的成员属性时,处理权交给了我们自己,而不再是PHP默认的报错处理方式。
第三种(对应3)
当删除一个不可访问的(非静态的)成员属性时;
PHP默认的处理方式是直接报错。
我们可以通过构建__unset魔术方法来对处理方式进行自定义。
Code13.php
当我们构建一个__unset魔术方法时,就相当于当我们删除不可访问的成员属性时,处理权交给了我们自己,而不再是PHP默认的报错处理方式。
第四种(对应4)
当判断一个不可访问的(非静态的)成员属性是否存在时;
PHP默认的处理方式是返回false,如下图:
我们可以构建一个__isset魔术方法来对处理方式进行自定义。
当我们构建一个__isset魔术方法时,就相当于当我们判断一个不可访问的成员属性是否存在时,处理权交给了我们自己,而不再是PHP默认的返回false的处理方式。
方法的重载
非静态方法的重载
默认的情况,当我们访问一个不可访问的非静态成员方法时,将会报错,如下图:
我们可以构建一个__call魔术方法来对处理方式进行自定义,需要传递两个参数,第一个参数为不可访问的方法的方法名,第二个参数为不可访问方法参数的数组集合。
Code15.php
当我们构建一个__call魔术方法时,就相当于当我们调用一个不可访问的非静态成员方法时,处理权交给了我们自己,而不再是PHP默认的报错处理方式。
静态方法的重载
默认的情况,当我们访问一个不可访问的静态成员方法时,将会,如下图:
我们可以构建一个__callstatic静态魔术方法来对处理方式进行自定义,需要传递两个参数,第一个参数为不可访问的静态方法的方法名,第二个参数为不可访问静态方法参数的数组集合。
当我们构建一个__callstatic静态魔术方法时,就相当于当我们调用一个不可访问的静态成员方法时,处理权交给了我们自己,而不再是PHP默认的报错处理方式。
小总结:
1, 以上所有的魔术方法,我们需要自己来手动定义,PHP不负责定义,只负责调用;
2, 我们在项目中使用的比较多的魔术方法是:__set, __get
十. 面向对象的三大特性
面向对象的三大特性:封装、继承和多态。
封装:通俗的理解就是“打包”,将相似或相近的功能“打包”封装到类中;
继承:指的就是PHP中的继承,我们PHP中类可以被继承。
多态:指的就是一种形式的多种实现方式。
十一.开发中常见的两种设计模式
所谓的设计模式,并不是固定的一种语法,而是开发人员在长期的开发工作中总结出来的一种设计经验。
单例模式
我们在通常的开发过程中,一般通过一个类实例化出的对象就能在程序中完成所有的类中方法的调用操作。那么,为了避免程序人员在程序中不断的去实例化新的对象(造成程序的额外的开销比较大),我们就需要使用“单例模式”来帮我们规避这个局限。
实现过程
第一步,将被类的构造方法私有化,防止在类的外部通过new实例化对象。(控制所有)
第二步,构建一个公有的静态的成员方法,再构建一个静态的成员属性,在公有的静态成员方法中利用静态成员属性保存对象并判断是否已经存在对象。
第三步,我们还需要在本类中,将克隆魔术方法私有化,防止在外部通过克隆的到一个全新的对象。
我们把这个单例模式的实现方式又称为:三私一公
最终实现的效果:
工厂模式
所谓的工厂模式,指的就是专门生产对象的。
程序实现
我们可以封装一个Factory工厂类,在工厂类中构建一个produce专门生产对象的方法。
十二.对象的遍历
数组和对象都属于复合数据类型。
对象是将数据保存在成员属性中的,我们还可以通过对象中的方法来操作属性,所以,我们也把对象称为有生命力的数组。
默认的对象的遍历
在PHP中,默认的对象的遍历,其实就是遍历出对象的属性名和对象的属性值。
自定义的对象的遍历
所谓的自定义的对象的遍历,其实就是遍历对象中的某个属性的值,而这个属性值是一个数组。
所以,归根到底,自定义的对象的遍历其实就是遍历数组。
我们要实现自定义的对象的遍历,就必须先实现一个PHP中的一个接口:Iterator
程序实现
Code27.php
十三.对象的序列化与反序列化
通过测试,我们发现,PHP中,除了字符串类型外,(整型,浮点,布尔,资源,对象,数组)这些数据类型保存进文件后再取出来,数组将会失真。
我们可以通过序列化与反序列化来帮助我们,让存进去的数据与取出来的数据保持一致(数据内容与数据类型都一致)。
序列化:序列化其实就是将数据以一定格式固定下来,同时记录数据数据类型和数据的值。
反序列化:将序列化之后的结果还原回原来的数据和数据类型。
注意:整型,浮点,布尔,资源,数组中,除了资源类型通过序列化与反序列化之后会变成整型0,其余都能正常还原。
演示文件:code2.php~code8.php
对象的序列化
我们可以通过serialize函数将对象序列化。如下图:
Code9.php
对象的反序列化
我们可以通过unserialize函数将对象反序列化。如下图:code9.php
__sleep魔术方法
当我们对一个对象使用serialize函数进行序列化的时候,PHP会自动调用并执行一个名为”__sleep”的魔术方法。
注意:PHP不负责定义这个魔术方法,只负责调用这个魔术方法。
__wakeup魔术方法
注意:当我们反序列化一个对象的时候,需要依赖于对象的原始类,如果在反序列化之前没有找到这个对象的原始类的定义,那么将会将这个对象反序列化为PHP默认的对象。如下图:
正因为反序列化的时候需要依赖于原始类的定义,所以,如果反序列化的过程中,发现缺少某个成员属性时,会将类中该属性和他的默认值一并拿过来反序列化到结果当中,如下图:
Code12.php
当我们对一个序列化的结果使用unserialize函数进行反序列化的时候,PHP会自动调用并执行一个名为”__wakeup”的魔术方法。
注意:PHP不负责定义这个魔术方法,只负责调用这个魔术方法。
我们通常在__wakeup魔术方法中,将序列化时排除的成员属性进行初始化工作。如下图。
案例:MYSQL操作类对象的序列化与反序列化
Source/MysqlDB.class.php code13.php code14.php
十四.反射机制
通常,我们可以通过一些简单的函数获得一个类当中的简单的信息,比如:get_class函数、get_class_methods函数。
但是,这些信息略显单薄,我们很多情况下希望获得更加详细的类中的信息,比如获得这个类定义在哪个文件下,这个类当中的各种成员都有哪些,那么,在PHP中,我们就能够通过反射机制来获得这些我们想要的更加详细的信息。
打个通俗的比喻,我们将反射机制视为:照妖镜、X光机。
反射的分类
1) 类的反射 2)类常量的反射 3)方法的反射 4)属性的反射 5)方法参数的反射
我们反射需要通过使用PHP封装好的一些反射相关的类来进行操作。
我们使用这些PHP封装好的类,其实就和我们使用普通的类没有任何区别,我们可以像操作普通类一样操作PHP封装好的这些类。
类的反射
ReflectionClass::export静态成员方法
反射出类中的详细信息
注意:方法的第一个参数不仅可以传对象还可以传类名。两种方式效果一样。
第二个参数如果不设置或者设置为false表示默认情况,默认情况是直接将反射的内容输出到浏览器。
如果设置为true,将会把反射的内容作为字符串返回。
Code16.php
GetFileName非静态的成员方法
返回类或对象所在的文件全路径名
ImplementsInterface非静态成员方法
用来判断某个类是否实现了某个接口,如果实现了返回true,如果没有实现则返回false。
Code18.php
类常量的反射
GetConstants非静态成员方法
用来返回类中定义的所有常量。
Code19.php
类的属性的反射
GetProperties非静态的成员方法
用来获取类中所有定义好的属性的信息的,返回的是一个数组。
Code20.php
类的方法的反射
GetMethods非静态成员方法
用来获得类中所有定义好的(非静态与静态)成员方法。
Code21.php
类方法的参数反射
ReflectionMethod::export静态成员方法
这个方法需要传递两个必填参数,第一个参数为类的类名,第二个参数为类中某个方法的方法名。
用来返回类中某个方法中的形参以及他的参数的默认值等信息。
案例:应用反射打印支付列表
通常实现方式
Code23.php
使用反射的实现形式
Code24.php
十五.命名空间
命名空间不是一个新的语法,最早是在我们的C#编程语言当中出现。命名空间语法发展到目前已经是一个比较成熟的语法体系。
市面上有一款主流的框架使用到了命名空间技术:TP(THINKPHP)
问题:我们在电脑中能否命名两个名字相同的文件?
回答:可以。我们只需要在不同的文件夹下命名同名的文件即可。
我们的硬盘能够通过磁盘分区技术划分不同的硬盘空间。
相应的,我们很多时候,也想在同一个程序脚本当中划分不同空间,以便达到定义重名函数,类等程序结构体的目的。
空间成员
命名空间包括三个部分的成员:1)const定义的常量 2)函数 3)类
语法
我们需要通过固定的语法格式来定义命名空间,
语法:namespace 命名空间名;
定义中注意的问题:如果使用了命名空间,则命名空间的定义一定要在脚本程序的最上端开始定义,如下图,在定义namespace first上面不能出现任何的脚本代码。
Code25.php
全局空间和子空间
全局空间
我们定义命名空间,默认的最上层的空间就是全局空间,在程序中我们以最前端的”\”来表示,后面的”\”都是分隔空间的意思,表示在xxx空间之下。
子空间
命名空间的三种访问方式
非限定名称访问
其实就是不在访问的成员前面指定空间名。
Code29.php
完全限定名称访问
其实就是从全局空间开始一直指定到成员所在的空间为止。
限定名称访问
空间的引入
问:为什么我们最终输出了first-f1呢?
答:如下图注释。
空间类的引入
语法:use 空间名\类名;
实现: