类是抽象的,是泛指,是某一类事物的统称
对象是具体的,是特指,是某一个具体事物的“特称”
Dog就是类,$d1就是对象。$d1是通过Dog“new”出来的(一个类通过new就可以得到一个对象)。
其中,类的名称Dog不区分大小写,但$d1作为变量,是区分大小的!
提示:
每个对象都会由系统给其分配一个“对象编号”,该对象编号在当前运行的程序中是唯一的不重复的。
类的继承
子类:
继承过程中,新的那个类,就是子类。也有其他一些类似称呼:派生类,下层类。
父类:
继承过程中,原有那个类,就是父类。也有其他一些类似称呼:基类,上层类,超类。
扩展:
子类继承了父类之后,再在自己的内部定义一些自己的成员信息的过程,就是扩展。
扩展,是继承的“自然目的”,即,继承,是为了进行适当的扩展。
如果继承了而不扩展,继承是没有意义的(子类跟父类一样,甚至有时候会更少功能)。
访问控制修饰符
出于安全和权限控制的需要,可以对一个类的成员的“可访问性”(可操作性)进行不同层级的控制。
PHP中,有3种不同层级的访问控制:
public(公共的):
表示该成员“哪里都可以访问”。之前的var其实就是public的同名词。
方法前没有加访问控制修饰符时,默认就是public
protected(受保护的):
表示该成员“只在自己内部或跟自己有继承关系的类内部可以访问”。
private(私有的):
表示该成员“只有自己可以访问”,即只能在该类的内部访问该成员。
重写(覆盖)override
基本含义:
重写就是指,在子类内部,对父类的同名成员,进行“重新定义”。
重写又叫“覆盖”,单词为“override”
为什么需要重写:
重写是由于子类继承父类后,“觉得”父类所给出的一些成员不能完全符合子类的需要,但又想要用到跟父类中同名的成员名,因此就在子类中重新定义该成员。
重写的基本要求:
重写时,子类成员的访问控制范围不能更小;
父类中成员是public,子类只能是public
父类中成员是protected, 子类同名成员可以是public和protected
父类中成员是private,子类并不能覆盖它!——会继承下来,但也不能用!
方法重写时,子类方法的形参需要跟父类保持一致,但构造方法的形参不受此限制
构造方法和析构方法的重写特征
1,当子类没有自己的构造方法的时候(自然会继承父类的构造方法),则实例化子类的时候,会自动调用父类的构造方法。
2,构造方法重写时的参数要求,不受限制!
3,析构方法类似:子类没有,就会自动调用父类的,否则就不会调用父类的。
abstract:抽象类和抽象方法
抽象类:
基本含义:
表示这个类“不能实例化对象”。
定义形式:
abstract class 类名 { 。。。。类成员。。。}
应用目的:
某人定义了一个类,并且希望(实际是要求)别人去继承这个类并实现具体的功能。
抽象方法:
基本含义:
表示这个方法是“不能执行的”,其实根本就是一个“空方法”,没有方法体。
定义形式:
abstract function 方法名(形参列表...);
应用目的:
某人定义了一个方法,并且希望(实际是要求)别人去继承这个类并实现具体的功能。
特别注意:
抽象方法其实只是定义了一个方法的“形式”,而完全没有方法的具体代码(方法体)。该形式的本质含义其实就是“要求”别人(下级类的定义者)要按这个抽象类的形式定义具体的方法(也就是所谓实现该方法)。因此,一个类如果继承了一个抽象类,则在语法要求上做到:
1,要么这个子类全部实现上级抽象类中的所有抽象方法。
2,要么这个子类本身也是抽象类——此时可以去实现或不实现上级的全部或部分抽象方法。
3,下级类实现上级类的抽象方法,本质其实就是覆盖
抽象类与抽象方法的关系:
1,抽象方法必须在抽象类中——即定义一个抽象方法,则必然会定义一个抽象类。
2,抽象类中可以没有抽象方法——但并不常见。
3,抽象类的目的是“供其他类继承”,抽象方法的目的是“供子类覆盖”。
关键字:
class, new, $this, self, parent, static, __construct, __destruct, extends, public, protected private, final, abstract
非关键字(但常见常用):
property属性, method方法, override重写(覆盖), declare(声明/定义), implements(实现)
接口interface
基本含义:
接口是一种比抽象类更抽象的特殊类——只是被称为接口。
它具有这样的特征:
接口中只可以有两种成员:常量(即接口常量),以及抽象方法。
为什么需要接口
接口是实现“多继承”的一种折中方案。
因为PHP中,类之间的关系只能是单继承,即一个类只能继承一个上级类的特征信息。
但如果一个类想要多个上级类的特征信息的时候,就可以做成“接口”,让类去“继承”多个接口。
一个类可以“继承”多个接口,也就具有了多个接口中具有的特征信息。
但一个类继承接口,就不叫继承了,而是被称为“实现”——本质其实就是继承。
举例:
音乐播放器:开始,暂停,停止,下一首,上一首,
USB存储器:宽,高,输出,存入
MP3播放器:它具有上述两个设备的功能!
接口的实现implements
基本含义:
一个类继承一个(或多个)接口内的特征信息,被称为“实现接口”。
语法形式:
class 类名 implements 接口1,接口2,.... {
。。。。类中成员。。。
}
基本要求:
一个类实现一个接口,因其本质是继承,那么,此时对这个类也有类似一个类继承一个抽象类的要求:
1,要么这个子类全部实现上级接口的所有抽象方法;
2,要么这个子类本身是抽象类——此时可以实现上级的部分抽象方法,或一个都不实现。
总结
面向对象三大特征
封装
广义的封装含义:
类本身就是一个封装体,是对属性(数据)和方法(对数据的处理行为)的封装。
狭义的封装含义:
是指设计一个类的时候,灵活使用合适的访问控制修饰符,以使外界对内部成员的访问尽可能少。
封装的目的是尽可能减少外界对类的内部的“任意操作行为”。
通常我们说的封装,都是指狭义的封装。
通常,对一个类的封装,不是“封装还是不封装的问题”,而是封装的程度问题。
举例: 对于一个“人类”年龄数据的更好设计——封装起来并进行有效判断。 class Person { public $name; //这一项可以无需特别处理 private $age = 18; //这一项需要特别处理 public function setAge($value) { if($value < 18 || $value > 60) { trigger_error('年龄无效!', E_USER_ERROR); } else { $this->age = $value; } } public function getAge() { return $this->age; } function showInfo() { echo '<br />被保险人年龄为:' . $this->age; } } $p1 = new Person(); //$p1->age = 30;//私有化属性后,无法执行 $p1->setAge(30); echo $p1->age; $p1->showInfo();
继承
继承是代码重用的重要方式。通过继承,我们可以轻松做到:
1,保留原有已经设计好的功能(代码),并可以直接使用。
2,完善(修改)原有已经设计好的部分功能,而保留其他无需修改的部分——通过覆盖来做到。
3,扩展原来设计的功能不够的部分——通过子类中添加新的成员来做到。
多态
指的是行为的多种表现形态。而行为就是“方法”,也就是说,一个方法会根据不同的情形,而表现出不同的“形态”(结果)。比如“吃”这个行为,人吃,猴子吃,猪吃,就会有不同的表现形态。
“人吃”的表现形态:使用手拿碗筷进行——优雅的样子;
“猴子吃”的表现形态:使用爪子直接抓取食物进行——猴急的样子;
“猪吃”的表现形态:直接使用嘴巴咬食物——难看的样子;
多态通常表现为如下两种情形:
方法重写:不同的对象可以访问相同的方法,但却有不同的表现形态(结果);
方法重载:同一个对象访问同一个方法但所给参数不同,也可以有不同的表现形态(结果)
举例:
动物类的“移动”方法,对于人,老虎,蛇,体现出不同的表现形态。
PHP的重载
其他面向对象语言中重载的概念:
指的是,在一个类的内部,定义多个同名不同参的方法,这种现象称为重载。
PHP语言中重载的概念:
当对一个对象或类的不存在的属性或方法进行访问(操作)的时候,PHP内部会自动调用某些预先定义好的魔术方法。PHP语言的这种机制,称为重载。
PHP重载分为“属性重载”和“方法重载”。
类的自动加载
当一条运行的语句需要一个类的时候(比如new的时候),而此时代码中尚未出现(给出)这个类的定义,则系统会自动调用一个预先定义好的“自动加载函数”,我们可以在这个时候(“趁此机会”)在该函数中将所需要的类的定义加载进来,那么该语句就可以正常执行下去,否则就出错。
自动加载的实现有一个特别的好处就是:按需加载
使用系统的自动加载函数 __autoload
类的自动加载小结:
1,需要一个类的时候,系统会去找一个自动加载函数(__autoload),并执行;
2,我们需要预先定义该函数并在该函数中完成加载所需要的类文件的工作,则通常应该遵循:
所需要的类的名称,跟其所在的文件名有一定的“关系”。
3,通常,需要自动加的类(文件),会统一按某种格式命名并统一存放在某位置。
4,并且,通常一个类使用一个独立的文件来写,通常此时称为“类文件”。
对象遍历
遍历语法跟数组遍历一样。
只能遍历所在位置能访问到的实例属性。
foreach($obj as $key => $value){ ..... }
自定义遍历对象
自定义遍历对象可以实现两个方面的遍历:
1,可以自己设定需要遍历的属性;
2,可以自己设定遍历的顺序。
要想实现自定义遍历对象,需要该类去实现一个系统的“迭代器”接口(iterator)。
该接口有如下5个方法:key(), current(), next(), rewind(), valid(),分别实现这5个方法,其含义如下:
key():用于返回当前属性的名;
current():用于返回当前属性的值;
next():用于返回下一项的值;
rewind():用于将遍历指针恢复到初始位置,即要遍历的属性的第一项(类似数组的第一个单元);
valid():用于返回当前指针“是否有效”,返回结果是true或false
演示案例:
class A implements iterator{ public $p1 = 11; public $p2 = 2; public $p3 = 13; public $p4 = 4; public $p5 = 1; private $arr = array();//用于存放要遍历的这些属性 private $pos = null;//用于存放要遍历的这些属性的“当前属性”的位置 //对象一实例化,就将要遍历的属性放入一个数组 function __construct() { $list = array('p1','p2','p3','p4','p5'); //将要遍历的这些属性,都放入私有数组arr中 foreach($list as $value){ $this->arr[$value] = $this->$value; } asort($this->arr);//对该数组排序,并保持下标不变 $this->pos = key($this->arr); } //用于返回当前属性的名; function key() return $this->pos; } //用于返回当前属性的值; function current() { echo $v1; return current($this->arr); } //用于返回下一项的值; function next() { next($this->arr); $this->pos = key($this->arr); return current($this->arr); //也可以这样: //return $this->arr[$this->pos]; } //用于将遍历指针恢复到初始位置, //即要遍历的属性的第一项(类似数组的第一个单元); function rewind() { reset($this->arr); $this->pos = key($this->arr); } //用于返回当前指针“是否有效”,返回结果是true或false function valid() { $result = $this->pos ? true : false; return $result; } } $a1 = new A(); foreach($a1 as $key => $value) { echo "<br />$key >>>> $value"; }
对象序列化
序列化:
就是将变量数据(内存数据),转换为硬盘数据(持久数据)的过程。
反序列化:
就是将硬盘数据(持久数据),恢复为变量数据(内存数据)的过程。
一般数据的序列化
这里说的一般数据,包括标量数据,和数组数据。
具体做法分两步:
1,将变量数据使用serialize()函数进行处理,获得处理后的结果(各种类型的数据处理后都是一个字符)。
2,使用file_put_contents()将该处理后的结果字符串,保存为文件。
一般数据的反序列化
跟上述序列化数据正好相反,也分两步:
1,使用file_get_contents()函数将文件中的数据取出来(结果是一个字符串);
2,将该字符串使用unserialize()进行处理,得到的结果就是原来的变量数据(就是内存数据)。
对象数据的序列化
基本含义:
对象数据的序列化,具体做法其实跟一般数据的序列化的做法是一样的。
只是,对于对象数据,我们可以(能够)在设计类的时候,对序列化工作做一些干预(或控制),或进行一些其他工作。
其中使用的就是__sleep()这个魔术方法。
__sleep()魔术方法介绍:
当一个对象被序列化的时候,会自动调用在类中预先定义好的该魔术方法。
我们可以“趁此机会”在这个时候去完成一些工作(如果需要)。
并且,此方法中,还可以根据需要,人为设定该对象需要进行序列化的属性(即不需要的属性就不会序列化了)。
特别注意:
如果定义了该方法,就需要在该方法中返回一个数组,且该数组的数据就是这个类(的对象)需要进行序列化存盘的属性名。
有关类和对象的函数
检查类是否定义class_exists()
检查接口是否存在interface_exists()
检查方法是否存在method_exists()
检查属性是否存在property_exists()
返回对象的类名get_class()
返回父类名称get_parent_class()