作者:高洛峰 来源:《PHPer》杂志
本部分一共包括以下一些技术点:
1.
面向对象的概念
2.
什么是类,什么是对象,类和对象这间的关系
3.
什么是面向对象编程呢?
4.
如何抽象出一个类?
5.
如何实例化对象?
6.
如何去使用对象中的成员?
7.
特殊的引用“$this”的使用
8.
构造方法与析构方法
9.
封装性
10.__set()
、 __get()、 __isset()、 __unset()四个方法的应用
11.
类的继承
12.
多态的应用
13.
重载新的方法
14.
访问类型
15.
静态成员的使用
16.final
关键字的应用
17.__toString()
方法
18.
克隆对象
19.__call
处理调用错误
20.
自动加载类
21.
把对象串行化
22.
抽象方法和抽象类
23.php5
接口技术
1.面向对象的概念
面向对象编程(Object Oriented Programming, OOP, 面向对象程序设计)是一种计算机编程架构,OOP的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成,OOP达到了软件工程的三个目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。面向对象一直是软件开发领域内比较热门的话题,首先,面向对象符合人类看待事物的一般规律。其次,采用面向对象方法可以使系统各部分各司其职、各尽所能。为编程人员敞开了一扇大门,使其编程的代码更简洁、更易于维护,并且具有更强的可重用性。有人说PHP不是一个真正的面向对象的语言,这是事实。PHP 是一个混合型语言,你可以使用OOP,也可以使用传统的过程化编程。然而,对于大型项目,你可能需要在PHP 中使用纯的OOP去声明类,而且在你的项目里只用对象和类。这个概念我先不多说了,因为有很多朋友远离面向对象编程的主要原因就是一接触面向对象概念的时候就理解不上去,所以就不想去学下去了。等读者看完整体内容后再去把概念搞明白吧。
2.什么是类,什么是对象,类和对象这间的关系
类的概念:类是具有相同属性和服务的一组对象的集合。它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性说明和服务说明两个主要部分。
对象的概念:对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。
类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类。类描述了一组有相同特性(属性)和相同行为(方法)的对象。
上面大概就是它们的定义吧,也许你是刚接触面向对象的朋友, 不要被概念的东西搞晕了,给你举个例子吧,如果你去中关村想买几台组装的PC机,到了那里你第一步要干什么,是不是装机的工程师和你坐在一起,按你提供的信息和你一起完成一个装机的配置单呀,这个配置单就可以想象成是类,它就是一张纸,但是它上面记录了你要买的PC机的信息,如果用这个配置单买10台机器,那么这10台机子,都是按这个配置单组成的,所以说这10台机子是一个类型的,也可以说是一类的。那么什么是对象呢,
类的实例化结果就是对象,用这个配置单
配置出来(实例化出来)的机子就是对象,是我们可以操作的实体,10台机子,10个对象。每台机子都是独立的,只能说明他们是同一类的,对其中一个机做任何动作都不会影响其它9台机器,但是我对类修改,也就是在这个配置单上加一个或少一个配件,那么装出来的9个机子都改变了,这是类和对象的关系(
类的实例化结果就是对象
)
。
3.什么是面向对象编程呢?
就不说他的概念,如果你想建立一个电脑教室,首先要有一个房间,
房间里面要有
N
台电脑,有
N
张桌子,
N
把椅子,
白板,
投影机等等,这些是什么,刚才咱们说了,
这就是对象,能看到的一个个的实体,可以说这个电脑教室的单位就是这一个个的实体对象,
它们共同组成了这个电脑教室,那么我们是做程序,这和面向对象有什么关系呢?开发一个系统程序和建一个电脑教室类似,你把每个独立的功能模块抽象成类,形成对象,由多个对象组成这个系统,这些对象之间都能够接收信息、处理数据和向其它对象发送信息等等相互作用。就构成了面向对象的程序。
4.如何抽象出一个类?
上面已经介绍过了,面向对象程序的单位就是对象,但对象又是通过类的实例化出来的,所以我们首先要做的就是如何来声明类,做出来一个类很容易,只要掌握基本的程序语法定义规则就可以做的出来,那么难点在那里呢?一个项目要用到多少个类,用多少个对象,在那要定义类,定义一个什么样的类,这个类实例化出多少个对象,类里面有多少个属性,有多少个方法等等,这就需要读者通过在实际的开发中就实际问题分析设计和总结了。
类的定义:
class 类名
{
}
使用一个关键字class和后面加上一个你想要的类名以及加上一对大括号, 这样一个类的结构就定义出来了,只要在里面写代码就可以了,但是里面写什么? 能写什么?怎样写才是一个完整的类呢?上面讲过来,使用类是为了让它实例出对象来给我们用,这就要知道你想要的是什么样的对象了,像上面我们讲的一个装机配置单上写什么,你装出来的机子就有什么。比如说,一个人就
是一个对象,你怎么把一个你看好的人推荐给你们领导呢?当然是越详细越好了:
首先,你会介绍这个人姓名、性别、年龄、身高、体重、电话、家庭住址等等。
然后,你要介绍这个人能做什么,可以开车,会说英语,可以使用电脑等等。
只要你介绍多一点,别人对这个人就多一点了解,这就是我们对一个人的描述, 现在我们总结
一下,所有的对象我们用类去描述都是类似的,从上面人的描述可以看到, 做出一个类来,从定义的角度分两部分,第一是从静态上描述,第二是从动态上描述,静态上的描述就是我们所说的
属性,像上面我们看到的,人的姓名、性别、年龄、身高、体重、电话、家庭住址等等。动态上也就是人的这个对象的功能,比如这个人可以开车,会说英语,可以使用电脑等等,抽象成程序时,我们把动态的写成函数或者说是方法,函数和方法是一样的。所以,所有类都是从属性和方法这两方面去写,属性又叫做这个类的成员属性,方法叫做这个类的成员方法。
class 人
{
成员属性:姓名、性别、年龄、身高、体重、电话、家庭住址
成员方法:可以开车, 会说英语, 可以使用电脑
}
属性:
通过在类定义中使用关键字" var "来声明变量,即创建了类的属性,虽然在声明成员属性的时候可以给定初始值, 但是在声明类的时候给成员属性初使值是没有必要的,比如说要是把人的姓名赋上“张三”,那么用这个类实例出几十个人,这几十个人都叫张三了,所以没有必要, 我们在实例出对象后给成员属性初始值就可以了。
如: var $somevar;
方法(成员函数):
通过在类定义中声明函数,即创建了类的方法。
如: function somefun(参数列表
)
{
... ... }
<?php
class
Person
{
//
下面是人的成员属性
var $
name
;
//
人的名子
var $
sex
;
//
人的性别
var $
age
;
//
人的年龄
//
下面是人的成员方法
function
say()
//
这个人可以说话的方法
{
echo
"
这个人在说话
"
;
}
function
run()
//
这个人可以走路的方法
{
echo
"
这个人在走路
"
;
}
}
?>
上面就是一个类的声明,从属性和方法上声明出来的一个类,但是成员属性最好在声明的时候
不要给初始的值,因为我们做的人这个类是一个描述信息,将来用它实例化对象,比如实例化出来10个人对象,那么这10个人, 每一个人的名子,性别, 年龄都是不一样的,所以最好不要在这个地方给成员属性赋初值,而是对每个对象分别赋值的。
用同样的办法可以做出你想要的类了, 只要你能用属性和方法能描述出来的实体都可以定义成类,去实例化对象。
为了加强你对类的理解,我们再做一个类,做一个形状的类,形状的范围广了点, 我们就做个矩形吧,先分析一下,想一想从两方面分析,矩形的属性都有什么?矩形的功能都有什么?
class
矩形
{
//矩形的属性
矩形的长;
矩形的宽;
//矩形的方法
矩形的周长;
矩形的面积;
}
<?php
class
Rect
{
var $
kuan
;
var $
gao
;
function
zhouChang()
{
计算矩形的周长;
}
function
mianJi()
{
计算矩形的面积;
}
}
?>
如果用这个类来创建出多个矩形对象,每个矩形对象都有自己的长和宽, 都可以求出自己的周长和面积了。
类的声明我们就到这里吧!!
5.如何实例化对象
我们上面说过面向对象程序的单位就是对象,但对象又是通过类的实例化出来的,既然我们类会声明了,下一步就是实例化对象了。
当定义好类后,我们使用new关键字来生成一个对象。
$
对象名称 = new 类名称();
<?php
class
Person
{
//
下面是人的成员属性
var $
name
;
//
人的名子
var $
sex
;
//
人的性别
var $
age
;
//
人的年龄
//
下面是人的成员方法
function
say()
//
这个人可以说话的方法
{
echo
"
这个人在说话
"
;
}
function
run()
//
这个人可以走路的方法
{
echo
"
这个人在走路
"
;
}
}
$
p1
=
new
Person();
$
p2
=
new
Person();
$
p3
=
new
Person();
?>
$p1=new Person();
这条代码就是通过类产生实例对象的过程,$p1就是我们实例出来的对象名称,同理,$p2, $p3也是我们实例出来的对象名称,一个类可以实例出多个对象,每个对象都是独立的,上面的代码相当于实例出来3个人来,每个人之间是没有联系的,只能说明他们都是人类,每个人都有自己的姓名,性别和年龄的属性,每个人都有说话和走路的方法,只要是类里面体现出来的成员属性和成员方法,实例化出来的对象里面就包含了这些属性和方法。
对像在PHP里面和整型、浮点型一样,也是一种数据类,都是存储不同类型数据用的,在运行的时候都要加载到内存中去用,那么对象在内存里面是怎么体现的呢?内存从罗辑上说大体上是分为4段, 栈空间段、堆空间段、代码段、 初始化静态段,程序里面不同的声明放在不同的内存段里面,栈空间段是存储占用相同空间长度并且占用空间小的数据类型的地方,比如说整型1, 10, 100, 1000, 10000, 100000等等,在内存里面占用空间是等长的,都是64位4个字节。 那么数据长度不定长,而且占有空间很大的数据类型的数据放在那内存的那个段里面呢?这样的数据是放在堆内存里面的。栈内存是可以直接存取的,而堆内存是不可以直接存取的内存。对于我们的对象来数就是一种大的数据类型而且是占用空间不定长的类型,所以说对象是放在堆里面的,但对象名称是放在栈里面的,这样通过对象名称就可以使用对象了。
$p1=new Person();
对于这个条代码, $p1是对象名称在栈内存里面,new Person()是真正的对象是在堆内存里面的,具体的请看下图:
![](http://www.lupaworld.com/attachment/070814/95514d4e27.gif)
从上图可以看出$p1=new Person();等号右边是真正的对象实例,在堆内存里面的实体,上图一共有3次new Person(),所以会在堆里面开辟3个空间,产生3个实例对象,每个对象之间都是相互独立的,使用自己的空间,在PHP里面,只要有一个new这个关键字出现就会实例化出来一个对象,在堆里面开辟一块自己的空间。
每个在堆里面的实例对象是存储属性的,比如说,现在堆里面的实例对象里面都存有姓名、性别和年龄。每个属性又都有一个地址。
$p1=new Person();
等号的右边$p1是一个引用变量,通过赋值运算符“=”把对象的首地址赋给“$p1”这个引用变量,所以$p1是存储对象首地址的变量,$p1放在栈内存里边,$p1相当于一个指针指向堆里面的对象,所以我们可以通过$p1这个引用变量来操作对象,通常我们也称对象引用为对象。
6.如何去使用对象中的成员
上面看到
PHP
对象中的成员有两种一种是成员属性,一种是成员方法。对象我们已经可以声明
了,
$p1=new Person();
怎么去使用对象的成员呢?要想访问对象中的成员就要使用一个特殊的操作符“
->
”来完成对象成员的访问:
对象
->
属性
$p1->name; $p2->age; $p3->sex;
对象
->
方法
$p1->say(); $p2->run();
如下面实例:
如下面实例:
<?php
class
Person
{
//
下面是人的成员属性
var $
name
;
//
人的名子
var $
sex
;
//
人的性别
var $
age
;
//
人的年龄
//
下面是人的成员方法
function
say()
//
这个人可以说话的方法
{
echo
"
这个人在说话
"
;
}
function
run()
//
这个人可以走路的方法
{
echo
"
这个人在走路
"
;
}
}
$
p1
=
new
Person();
//
创建实例对象
$p1
$
p2
=
new
Person();
//
创建实例对象
$p2
$
p3
=
new
Person();
//
创建实例对象
$p3
//
下面三行是给
$p1
对象属性赋值
$
p1
->name=
”
张三
”
;
$
p1
->sex=
”
男
”
;
$
p1
->age=
20
;
//
下面三行是访问
$p1
对象的属性
echo
“
p1
对象的名子是:
”
.
$
p1
->name.
”
<br>
”
;
echo
“
p1
对象的性别是:
”
.
$
p1
->sex.
”
<br>
”
;
echo
“
p1
对象的年龄是:
”
.
$
p1
->age.
”
<br>
”
;
//
下面两行访问
$p1
对象中的方法
$
p1
->say();
$
p1
->run();
//
下面三行是给
$p2
对象属性赋值
$
p2
->name=
”
李四
”
;
$
p2
->sex=
”
女
”
;
$
p2
->age=
30
;
//
下面三行是访问
$p2
对象的属性
echo
“
p2
对象的名子是:
”
.
$
p2
->name.
”
<br>
”
;
echo
“
p2
对象的性别是:
”
.
$
p2
->sex.
”
<br>
”
;
echo
“
p2
对象的年龄是:
”
.
$
p2
->age.
”
<br>
”
;
//
下面两行访问
$p2
对象中的方法
$
p2
->say();
$
p2
->run();
//
下面三行是给
$p3
对象属性赋值
$
p3
->name=
”
王五
”
;
$
p3
->sex=
”
男
”
;
$
p3
->age=
40
;
//
下面三行是访问
$p3
对象的属性
echo
“
p3
对象的名子是:
”
.
$
p3
->name.
”
<br>
”
;
echo
“
p3
对象的性别是:
”
.
$
p3
->sex.
”
<br>
”
;
echo
“
p3
对象的年龄是:
”
.
$
p3
->age.
”
<br>
”
;
//
下面两行访问
$p3
对象中的方法
$
p3
->say();
$
p3
->run();
?>
从上例中可以看出只是对象里面的成员就要使用
对象
->
属性
、对象
->
方法
形式访问,再没有第二种方法来访问对象中的成员了。
7.特殊的引用“$this”的使用
现在我们知道了如何访问对象中的成员,是通过“对象
->
成员”的方式访问的
,
这是在对象的外部去访问对象中成员的形式,那么如果我想在对象的内部,让对象里的方法访问本对象的属性,或是对象中的方法去调用本对象的其它方法这时我们怎么办?因为对象里面的所有的成员都要用对象来调用,包括对象的内部成员之间的调用,所以在
PHP
里面给我提供了一个本对象的引用
$this
,
每个对象里面都有一个对象的引用
$this
来代表这个对象,完成对象内部成员的调用
, this
的本意就是“这个”的意思,
上面的实例里面,我们实例化三个实例对象
$P1
、
$P2
、
$P3
,这三个对象里面各自存在一个
$this
分别代表对象
$p1
、
$p2
、
$p3
。
![](http://www.lupaworld.com/attachment/070814/7db16d6bb1.gif)
通过上图我们可以看到,
$this
就是对象内部代表这个对象的引用,在对象内部和调用本对象的成员和对象外部调用对象的成员所使用的方式是一样的。
$this->
属性
$this->name; $this->age; $this->sex;
$this->
方法
$this->say(); $this->run();
修改一下上面的实例,让每个人都说出自己的名字,性别和年龄:
修改一下上面的实例,让每个人都说出自己的名字,性别和年龄:
<?php
class
Person
{
//
下面是人的成员属性
var $
name
;
//
人的名子
var $
sex
;
//
人的性别
var $
age
;
//
人的年龄
//
下面是人的成员方法
function
say()
//
这个人可以说话的方法
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
}
function
run()
//
这个人可以走路的方法
{
echo
"
这个人在走路
"
;
}
}
$
p1
=
new
Person();
//
创建实例对象
$p1
$
p2
=
new
Person();
//
创建实例对象
$p2
$
p3
=
new
Person();
//
创建实例对象
$p3
//
下面三行是给
$p1
对象属性赋值
$
p1
->name=
"
张三
"
;
$
p1
->sex=
"
男
"
;
$
p1
->age=
20
;
//
下面访问
$p1
对象中的说话方法
$
p1
->say();
//
下面三行是给
$p2
对象属性赋值
$
p2
->name=
"
李四
"
;
$
p2
->sex=
"
女
"
;
$
p2
->age=
30
;
//
下面访问
$p2
对象中的说话方法
$
p2
->say();
//
下面三行是给
$p3
对象属性赋值
$
p3
->name=
"
王五
"
;
$
p3
->sex=
"
男
"
;
$
p3
->age=
40
;
//
下面访问
$p3
对象中的说话方法
$
p3
->say();
?>
输出结果为:
我的名子叫:张三
性别:男
我的年龄是:
20
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
分析一下这个方法:
function
say()
//
这个人可以说话的方法
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
}
在$p1、$p2和$p3这三个对象中都有say()这个方法,$this分别代表这三个对象, 调用相应的属性,打印出属性的值,这就是在对象内部访问对象属性的方式, 如果相在say()这个方法里调用run()这个方法也是可以的,在say()这个方法中使用$this->run()的方式来完成调用。
8.构造方法与析构方法
大多数类都有一种称为构造函数的特殊方法。当创建一个对象时,它将自动调用构造函数,也就是使用
new
这个关键字来实例化对象的时候自动调用构造方法。
构造函数的声明与其它操作的声明一样,只是其名称必须是
__construct( )
。这是
PHP5
中的变化,以前的版本中,构造函数的名称必须与类名相同,这种在
PHP5
中仍然可以用,但现在以经很少有人用了,这样做的好处是可以使构造函数独立于类名,当类名发生改变时不需要改相应的构造函数名称了。为了向下兼容,如果一个类中没有名为
__construct( )
的方法,
PHP
将搜索一个
php4
中的写
法
,
与类名相同名的构造方法。
格式:
function __construct ( [
参数
] ) { ... ... }
在一个类中只能声明一个构造方法,而是只有在每次创建对象的时候都会去调用一次构造方法,不能主动的调用这个方法,所以通常用它执行一些有用的初始化任务。比如对成属性在创建对象的时候赋初值。
<?
//
创建一个人类
class
Person
{
//
下面是人的成员属性
var $
name
;
//
人的名子
var $
sex
;
//
人的性别
var $
age
;
//
人的年龄
//
定义一个构造方法参数为姓名
$name
、性别
$sex
和年龄
$age
function
__construct(
$
name
,
$
sex
,
$
age
)
{
//
通过构造方法传进来的
$name
给成员属性
$this->name
赋初使值
$
this
->name=
$
name
;
//
通过构造方法传进来的
$sex
给成员属性
$this->sex
赋初使值
$
this
->sex=
$
sex
;
//
通过构造方法传进来的
$age
给成员属性
$this->age
赋初使值
$
this
->age=
$
age
;
}
//
这个人的说话方法
function
say()
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
}
}
//
通过构造方法创建
3
个对象
$p1
、
p2
、
$p3,
分别传入三个不同的实参为姓名、性别和年龄
$
p1
=
new
Person(
“
张三
”
,
”
男
”
,
20
);
$
p2
=
new
Person(
“
李四
”
,
”
女
”
,
30
);
$
p3
=
new
Person(
“
王五
”
,
”
男
”
,
40
);
//
下面访问
$p1
对象中的说话方法
$
p1
->say();
//
下面访问
$p2
对象中的说话方法
$
p2
->say();
//
下面访问
$p3
对象中的说话方法
$
p3
->say();
?>
输出结果为:
我的名子叫:张三
性别:男
我的年龄是:
20
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
如图:
![](http://www.lupaworld.com/attachment/070814/6e9c128f72.gif)
析构函数:
与构造函数相对的就是析构函数。析构函数是
PHP5
新添加的内容,在
PHP4
中没有析构函数。析构函数允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件,释放结果集等,
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,也就是对象在内存中被销毁前调用析构函数。
与构造函数的名称类似,一个类的析构函数名称必须是
__destruct( )
。析构函数不能带有任何参数。
格式:
function __destruct ( ) { ... ... }
<?
//
创建一个人类
class
Person
{
//
下面是人的成员属性
var $
name
;
//
人的名子
var $
sex
;
//
人的性别
var $
age
;
//
人的年龄
//
定义一个构造方法参数为姓名
$name
、性别
$sex
和年龄
$age
function
__construct(
$
name
,
$
sex
,
$
age
)
{
//
通过构造方法传进来的
$name
给成员属性
$this->name
赋初使值
$
this
->name=
$
name
;
//
通过构造方法传进来的
$sex
给成员属性
$this->sex
赋初使值
$
this
->sex=
$
sex
;
//
通过构造方法传进来的
$age
给成员属性
$this->age
赋初使值
$
this
->age=
$
age
;
}
//
这个人的说话方法
function
say()
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
}
//
这是一个析构函数,在对象销毁前调用
function
__destruct()
{
echo
“
再见
”
.
$
this
->name.
”
<br>
”
;
}
//
通过构造方法创建
3
个对象
$p1
、
p2
、
$p3,
分别传入三个不同的实参为姓名、性别和年龄
$
p1
=
new
Person(
“
张三
”
,
”
男
”
,
20
);
$
p2
=
new
Person(
“
李四
”
,
”
女
”
,
30
);
$
p3
=
new
Person(
“
王五
”
,
”
男
”
,
40
);
//
下面访问
$p1
对象中的说话方法
$
p1
->say();
//
下面访问
$p2
对象中的说话方法
$
p2
->say();
//
下面访问
$p3
对象中的说话方法
$
p3
->say();
?>
输出结果为:
我的名子叫:张三
性别:男
我的年龄是:
20
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
再见张三
再见李四
再见王五
9.封装性
封装性是面向对象编程中的三大特性之一,
封装性就是把对象的属性和服务结合成一个独立的相同单位,并尽可能隐蔽对象的内部细节,包含两个含义
:1.
把对象的全部属性和全部服务结合在一起,形成一个不可分割的独立单位(即对象)。
2.
信息隐蔽,即尽可能隐蔽对象的内部细节,对外形成一个边界〔或者说形成一道屏障〕,只保留有限的对外接口使之与外部发生联系。
封装的原则在软件上的反映是:要求使对象以外的部分不能随意存取对象的内部数据(属性),从而有效的避免了外部错误对它的
"
交叉感染
"
,使软件错误能够局部化,大大减少查错和排错的难度。
用个实例来说明吧,假如某个人的对象中有年龄和工资等属性,像这样个人隐私的属性是不想让其它人随意就能获得到的,如果你不使用封装,那么别人想知道就能得到,但是如果你封装上之
后别人就没有办法获得封装的属性,除非你自己把它说出去,否则别人没有办法得到。
再比如说,个人电脑都有一个密码,不想让其它人随意的登陆,在你的电脑里面拷贝和粘贴。还有就是像人这个对象,身高和年龄的属性,只能是自己来增涨,不可以让别人随意的赋值等等。
使用
private
这个关键字来对属性和方法进行封装:
原来的成员
:
var $name; //
声明人的姓名
var $sex; //
声明人的性别
var $age; //
声明人的年龄
function run(){…….}
改成封装的形式:
private $name;
//
把人的姓名使用
private
关键字进行封装
private $sex;
//
把人的性别使用
private
关键字进行封装
private $age;
//
把人的年龄使用
private
关键字进行封装
private function run(){……}
//
把人的走路方法使用
private
关键字进行封装
注意:只要是成员属性前面有其它的关键字就要去掉原有的关键字“
var”
。
通过
private
就可以把人的成员(成员属性和成员方法)封装上了。封装上的成员就不能被类外面直接访问了,只有对象内部自己可以访问;下面的代码会产生错误:
class
Person
{
//
下面是人的成员属性
private $
name
;
//
人的名子,被
private
封装上了
private $
sex
;
//
人的性别
,
被
private
封装上了
private $
age
;
//
人的年龄
,
被
private
封装上了
//
这个人可以说话的方法
function
say()
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
}
//
这个人可以走路的方法
,
被
private
封装上了
private function
run()
{
echo
"
这个人在走路
"
;
}
}
//
实例化一个人的实例对象
$
p1
=
new
Person();
//
试图去给私有的属性赋值, 结果会发生错误
$
p1
->name=
"
张三
"
;
$
p1
->sex=
"
男
"
;
$
p1
->age=
20
;
//
试图去打印私有的属性, 结果会发生错误
echo $
p1
->name.
”
<br>
”
;
echo $
p1
->sex.
”
<br>
”
;
echo $
p1
->age.
”
<br>
”
//
试图去打印私有的成员方法, 结果会发生错误
$
p1
->run();
输出结果为:
Fatal error: Cannot access private property Person::$name
Fatal error: Cannot access private property Person::$sex
Fatal error: Cannot access private property Person::$age
Fatal error: Cannot access private property Person::$name
Fatal error: Call to private method Person::run() from context ''
从上面的实例可以看到,
私有的成员是不能被外部访问的,
因为私有成员只能在本对象内部自己访问,比如,
$p1
这个对象自己想把他的私有属性说出去,在
say()
这个方法里面访问了私有属性,这样是可以。(没有加任何访问控制,默认的是
public
的,任何地方都可以访问)
//
这个人可以说话的方法
,
说出自己的私有属性,在这里也可以访问私有方法
function
say()
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
//
在这里也可以访问私有方法
//$this->run();
}
因为成员方法
say()
是公有的,
所以我们在类的外部调用
say()
方法是可以的,改变上面的代码;
class
Person
{
//
下面是人的成员属性
private $
name
;
//
人的名子,被
private
封装上了
private $
sex
;
//
人的性别
,
被
private
封装上了
private $
age
;
//
人的年龄
,
被
private
封装上了
//
定义一个构造方法参数为私有的属性姓名
$name
、性别
$sex
和年龄
$age
进行赋值
function
__construct(
$
name
,
$
sex
,
$
age
)
{
//
通过构造方法传进来的
$name
给私有成员属性
$this->name
赋初使值
$
this
->name=
$
name
;
//
通过构造方法传进来的
$sex
给私有成员属性
$this->sex
赋初使值
$
this
->sex=
$
sex
;
//
通过构造方法传进来的
$age
给私有成员属性
$this->age
赋初使值
$
this
->age=
$
age
;
}
//
这个人可以说话的方法
,
说出自己的私有属性,在这里也可以访问私有方法
function
say()
{
echo
"
我的名子叫:
"
.
$
this
->name.
"
性别:
"
.
$
this
->sex.
"
我的年龄是:
"
.
$
this
->age.
"<br>"
;
}
}
//
通过构造方法创建
3
个对象
$p1
、
p2
、
$p3,
分别传入三个不同的实参为姓名、性别和年龄
$
p1
=
new
Person(
“
张三
”
,
”
男
”
,
20
);
$
p2
=
new
Person(
“
李四
”
,
”
女
”
,
30
);
$
p3
=
new
Person(
“
王五
”
,
”
男
”
,
40
);
//
下面访问
$p1
对象中的说话方法
$
p1
->say();
//
下面访问
$p2
对象中的说话方法
$
p2
->say();
//
下面访问
$p3
对象中的说话方法
$
p3
->say();
输出结果为:
我的名子叫:张三
性别:男
我的年龄是:
20
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
我的名子叫:李四 性别:女 我的年龄是: 30
我的名子叫:王五 性别:男 我的年龄是: 40
因为构造方法是默认的公有方法(构造方法不要设置成私有的),所以在类的外面可以访问到,这样就可以使用构造方法创建对象,
另外构造方法也是类里面的函数,所以可以用构造方法给私有的属性赋初值。
Say()
的方法是默认公有的,
所以在外面也可以访问的到,
说出他自己的私有属性。
从上面的例子中我们可以看到,私有的成员只能在类的内部使用,不能被类外部直接来存取,但是在类的内部是有权限访问的,所以有时候我们需要在类的外面给私有属性赋值和读取出来,也就是给类的外部提供一些可以存取的接口,上例中构造方法就是一种赋值的形式,但是构造方法只是在创建对象的时候赋值,如果我们已经有一个存在的对象了,想对这个存在的对象赋值,这个时候,如果你还使用构造方法传值的形式传值,那么就创建了一个新的对象,并不是这个已存在的对象了。所以我们要对私有的属性做一些可以被外部存取的接口,目的就是可以在对象存在的情况下,改变和存取属性的值,但要注意,只有需要让外部改变的属性才这样做,不想让外面访问的属性是不做这样的接口的,这样就能达到封装的目的,所有的功能都是对象自己来完成,给外面提供尽量少的操作。
如果给类外部提供接口,可以为私有属性在类外部提供设置方法和获取方法,来操作私有属性
.
例如:
prvate
$
age
;
//
私有的属性年龄
function
setAge(
$
age
)
//
为外部提供一个公有设置年龄的方法
{
if
(
$
age
<
0
||
$
age
>
130
)
//
在给属性赋值的时候,为了避免非法值设置给属性
return
;
$
this
->age=
$
age
;
}
function
getAge()
//
为外部提供一个公有获取年龄的方法
{
return
(
$
this
->age);
}
上面的方法是为一个成员属性设置和获取值,
当然你也可以为每个属性用同样的方法对其进行赋值和取值的操作,完成在类外部的存取工作。
一般来说,总是把类的属性定义为
private
,这更符合现实的逻辑。但是,
对属性的读取和赋值操作是非常频繁的,因此在
PHP5
中,预定义了两个函数“
__get()
”和“
__set()
”来获取和赋值其属性
,
以及检查属性的“
__isset()
”和删除属性的方法“
__unset()
”。
上一节中,我们为每个属性做了设置和获取的方法,在
PHP5
中给我们提供了专门为属性设置值和获取值的方法,“
__set()
”和“
__get()
”这两个方法,这两个方法不是默认存在的,
而是我们手工添加到类里面去的,像构造方法
(__construct())
一样
,
类里面添加了才会存在,可以按下面的方式来添加这两个方法,当然也可以按个人的风格来添加:
//__get()
方法用来获取私有属性
private function
__get(
$
property_name
)
{
if
(
isset
(
$
this
->
$
property_name
))
{
return
(
$
this
->
$
property_name
);
}
else
{
return
(
NULL
);
}
}
//__set()
方法用来设置私有属性
private function
__set(
$
property_name
,
$
value
)
{
$
this
->
$
property_name
=
$
value
;
}
__get()
方法:这个方法用来获取私有成员属性值的
,
有一个参数,
参数传入你要获取的成员属性的名称,返回获取的属性值,
这个方法不用我们手工的去调用,因为我们也可以把这个方法做成私有的方法,
是在直接获取私有属性的时候对象自动调用的。因为私有属性已经被封装上了,是不能直接获取值的(比如:“
echo $p1->name
”
这样直接获取是错误的),但是如果你在类里面加上了这个方法,在使用“
echo $p1->name
”
这样的语句直接获取值的时候就会自动调用
__get($property_name)
方法,将属性
name
传给参数
$property_name
,通过这个方法的内部执行,返回我们传入的私有属性的值。如果成员属性不封装成私有的,对象本身就不会去自动调用这个方法。
__set()
方法:这个方法用来为私有成员属性设置值的,有两个参数,第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。这个方法同样不用我们手工去调用,它也可以做成私有的,是在直接设置私有属性值的时候自动调用的,同样属性私有的已经被封装上
了,
如果没有
__set()
这个方法,是不允许的,
比如:
$this->name=
‘
zhangsan
’
,
这样会出错,但是如果你在类里面加上了
__set($property_name, $value)
这个方法,在直接给私有属性赋值的时候,就会自动调用它,把属性比如
name
传给
$property_name,
把要赋的值“
zhangsan
”传给
$value
,通过这个方法的执行,达到赋值的目的。如果成员属性不封装成私有的,对象本身就不会去自动调用这个方法。为了不传入非法的值,
还可以在这个方法给做一下判断。代码如下:
<?php
class
Person
{
//
下面是人的成员属性, 都是封装的私有成员
private $
name
;
//
人的名子
private $
sex
;
//
人的性别
private $
age
;
//
人的年龄
//__get()
方法用来获取私有属性
private function
__get(
$
property_name
)
{
echo
"
在直接获取私有属性值的时候,自动调用了这个
__get()
方法
<br>"
;
if
(
isset
(
$
this
->
$
property_name
))
{
return
(
$
this
->
$
property_name
);
}
else
{
return
(
NULL
);
}
}
//__set()
方法用来设置私有属性
private function
__set(
$
property_name
,
$
value
)
{
echo
"
在直接设置私有属性值的时候,自动调用了这个
__set()
方法为私有属性赋值
<br>"
;
$
this
->
$
property_name
=
$
value
;
}
}
$
p1
=
new
Person();
//
直接为私有属性赋值的操作, 会自动调用
__set()
方法进行赋值
$
p1
->name=
"
张三
"
;
$
p1
->sex=
"
男
"
;
$
p1
->age=
20
;
//
直接获取私有属性的值, 会自动调用
__get()
方法,返回成员属性的值
echo
"
姓名:
"
.
$
p1
->name.
"<br>"
;
echo
"
性别:
"
.
$
p1
->sex.
"<br>"
;
echo
"
年龄:
"
.
$
p1
->age.
"<br>"
;
?>
程序执行结果:
在直接设置私有属性值的时候,自动调用了这个
__set()
方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个 __set() 方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个 __set() 方法为私有属性赋值
在直接获取私有属性值的时候,自动调用了这个 __get() 方法
姓名:张三
在直接获取私有属性值的时候,自动调用了这个 __get() 方法
性别:男
在直接获取私有属性值的时候,自动调用了这个 __get() 方法
年龄: 20
在直接设置私有属性值的时候,自动调用了这个 __set() 方法为私有属性赋值
在直接设置私有属性值的时候,自动调用了这个 __set() 方法为私有属性赋值
在直接获取私有属性值的时候,自动调用了这个 __get() 方法
姓名:张三
在直接获取私有属性值的时候,自动调用了这个 __get() 方法
性别:男
在直接获取私有属性值的时候,自动调用了这个 __get() 方法
年龄: 20
以上代码如果不加上
__get()
和
__set()
方法,程序就会出错,因为不能在类的外部操作私有成员,而上面的代码是通过自动调用
__get()
和
__set()
方法来帮助我们直接存取封装的私有成员的。
__isset()
方法:在看这个方法之前我们看一下“
isset()
”函数的应用,
isset()
是测定变量是否设定用的函数,传入一个变量作为参数,如果传入的变量存在则传回
true
,否则传回
false
。那么如果在一个对象外面使用“
isset()
”这个函数去测定对象里面的成员是否被设定可不可以用它呢?分两种情况,如果对象里面成员是公有的,我们就可以使用这个函数来测定成员属性,
如果是私有的成员属性,这个函数就不起作用了,原因就是因为私有的被封装了,在外部不可见。那么我们就不可以在对象的外部使用“
isset()
”函数来测定私有成员属性是否被设定了呢?可以,你只要在类里面加上一个“
__isset()
”方法就可以了,当在类外部使用
”isset()”
函数来测定对象里面的私有成员是否被设定时,
就会自动调用类里面的“
__isset()
”方法了帮我们完成这样的操作,“
__isset()
”方法也可以做成私有的。你可以在类里面加上下面这样的代码就可以了:
private function
__isset(
$
nm
)
{
echo
"
当在类外部使用
isset()
函数测定私有成员
$nm
时,自动调用
<br>"
;
return isset
(
$
this
->
$
nm
);
}
__unset()
方法:看这个方法之前呢,我们也先来看一下“
unset()
”这个函数,“
unset()
”这个函数的作用是删除指定的变量且传回
true
,参数为要删除的变量。那么如果在一个对象外部去删除对象内部的成员属性用“
unset()
”函数可不可以呢,
也是分两种情况,如果一个对象里面的成员属性是公有的,
就可以使用这个函数在对象外面删除对象的公有属性,
如果对象的成员属性是私有的,我使用这个函数就没有权限去删除,但同样如果你在一个对象里面加上“
__unset()
”这个方法,就可以在对象的外部去删除对象的私有成员属性了。在对象里面加上了“
__unset()
”这个方法之后,在对象外部使用“
unset()
”函数删除对象内部的私有成员属性时,自动调用“
__unset()
”函数来帮
我们删除对象内部的私有成员属性,这个方法也可以在类的内部定义成私有的。在对象里面加上下面的代码就可以了:
private function
__unset(
$
nm
)
{
echo
"
当在类外部使用
unset()
函数来删除私有成员时自动调用的
<br>"
;
unset
(
$
this
->
$
nm
);
}
我们来看一个完整的实例:
<?php
class
Person
{
//
下面是人的成员属性
private $
name
;
//
人的名子
private $
sex
;
//
人的性别
private $
age
;
//
人的年龄
//__get()
方法用来获取私有属性
private function
__get(
$
property_name
)
{
if
(
isset
(
$
this
->
$
property_name
))
{
return
(
$
this
->
$
property_name
);
}
else
{
return
(
NULL
);
}
}
//__set()
方法用来设置私有属性
private function
__set(
$
property_name
,
$
value
)
{
$
this
->
$
property_name
=
$
value
;
}
//__isset()
方法
private function
__isset(
$
nm
)
{
echo
"isset()
函数测定私有成员时,自动调用
<br>"
;
return isset
(
$
this
->
$
nm
);
}
//__unset()
方法
private function
__unset(
$
nm
)
{
echo
"
当在类外部使用
unset()
函数来删除私有成员时自动调用的
<br>"
;
unset
(
$
this
->
$
nm
);
}
}
$
p1
=
new
Person();
$
p1
->name=
"this is a person name"
;
//
在使用
isset()
函数测定私有成员时,自动调用
__isset()
方法帮我们完成,返回结果为
true
echo
var_dump
(
isset
(
$
p1
->name)).
"<br>"
;
echo $
p1
->name.
"<br>"
;
//
在使用
unset()
函数删除私有成员时,自动调用
__unset()
方法帮我们完成,删除
name
私有属性
unset
(
$
p1
->name);
//
已经被删除了, 所这行不会有输出
echo $
p1
->name;
?>
输出结果为:
isset()
函数测定私有成员时,自动调用
bool(true)
this is a person name
当在类外部使用 unset() 函数来删除私有成员时自动调用的
bool(true)
this is a person name
当在类外部使用 unset() 函数来删除私有成员时自动调用的
__set()
、
__get()
、
__isset()
、
__unset()
这四个方法都是我们添加到对象里面的,在需要时自动调用的,来完成在对象外部对对象内部私有属性的操作。