PHP从入门到精通(微视频精编版)
上QQ阅读APP看书,第一时间看更新

6章 面向对象

视频讲解:1小时21分钟

面向对象是一种计算机编程架构,比面向过程编程具有更强的灵活性和扩展性。面向对象编程也是一个程序员发展的“分水岭”,很多的初学者和略有成就的开发者,就是因为无法理解“面向对象”而放弃。这里想提醒一下初学者:要想在编程这条路上走得比别人远,就一定要掌握面向对象编程技术。

学习摘要:

面向对象的基本概念

PHP与对象

6.1 面向对象的基本概念

视频讲解

在前几章中,我们已经学习使用了字符串和数组组织数据,也学习了使用函数把一些代码收集到能够反复使用的单元中。本章介绍的对象(object)则让这种收集的思想更向前迈进一步。对象可以把函数和数据收集在一起。下面来了解一下面向对象的基本概念。

6.1.1 类的概念

世间万物都具有其自身的属性和方法,通过这些属性和方法可以将不同物质区分开来。例如,人具有身高、体重和肤色等属性,还可以进行吃饭、学习、走路等能动活动,这些活动可以说是人具有的功能。可以把人看作程序中的一个类,那么人的身高可以看作类中的属性,走路可以看作类中的方法。也就是说,类是属性和方法的集合,这是面向对象编程方式的核心和基础。通过类可以将零散的用于实现某项功能的代码进行有效管理。例如,创建一个运动类,包括5个属性:姓名、身高、体重、年龄和性别,定义4个方法:踢足球、打篮球、举重和跳高,如图6.1所示。

图6.1 运动类

6.1.2 对象的概念

类只是具备某项功能的抽象模型,实际应用中还需要对类进行实例化,这样就引入了对象的概念。对象是类进行实例化后的产物,是一个实体。仍然以人为例,“黄种人是人”这句话没有错误,但反过来说“人是黄种人”这句话一定是错误的。因为除了有黄种人,还有黑人、白人等。那么“黄种人”就是“人”这个类的一个实例对象。可以这样理解对象和类的关系:对象实际上就是“有血有肉的、能摸得到看得到的”一个类。

这里实例化6.1.1节中创建的运动类,如图6.2所示。

图6.2 实例化对象

6.1.3 面向对象编程的三大特点

面向对象编程的三大特点是封装性、继承性和多态性。

1.封装性

封装性,也可以称为信息隐藏。就是将一个类的使用和实现分开,只保留有限的接口(方法)与外部联系。对于用到该类的开发人员,只要知道这个类该如何使用即可,而不用去关心这个类是如何实现的。这样做可以让开发人员更多地把精力集中起来专注别的事情,同时也避免了程序之间相互依赖而带来的不便。这就像普通用户购买汽车,我们只需要知道如何驾驶汽车,并不需要去了解汽车内部的构造。

2.继承性

在真实的世界中,人们可以从他们的父母或者其他直系亲戚那里继承一些东西。例如,在图6.3中,“我”可以继承“爸爸”和“妈妈”财产。同理,“爸爸”也可以继承“祖父”和“祖母”的财产。继承性就是派生类(子类)自动继承一个或多个基类(父类)中的属性与方法,并可以重写或添加新的属性或方法。继承这个特性简化了对象和类的创建,增加了代码的可重用性。

图6.3 家族图谱

3.多态性

多态性是指对于不同的类,可以有同名的两个(或多个)方法。取决于这些方法分别应用到哪个类。例如,定义一个“汽车”类和一个“自行车”类,二者都可以具有不同的“移动”操作。多态性增强了软件的灵活性和重用性。

6.2 PHP与对象

视频讲解

6.2.1 类的定义

和很多面向对象的语言一样,PHP也是通过class关键字加类名来定义类的。类的格式如下:

上述两个大括号中间的部分是类的全部内容,如上述SportObject就是一个最简单的类。SportObject类仅有一个类的骨架,什么功能都没有实现,但这并不影响它的存在。

注意

一个类,即一对大括号之间的全部内容都要在一段代码段中,即一个“<?php …?>”之间不能分割成多块,例如,下面的格式是不允许的:

6.2.2 成员方法

类中的函数被称为成员方法。函数和成员方法唯一的区别就是,函数实现的是某个独立的功能,而成员方法是实现类中的一个行为,是类的一部分。

下面就创建在图6.1中编写的运动类,并添加成员方法。将类命名为SportObject,并添加打篮球的成员方法beatBasketball()。代码如下:

该方法的作用是输出申请打篮球人的基本信息,包括姓名、身高和年龄。这些信息是通过方法的参数传进来的。

6.2.3 类的实例化

定义完类和方法后,并不会真正创建一个对象。这有点像一辆汽车的设计图。设计图可以告诉我们汽车长什么样,但设计图本身不是一辆汽车。我们不能开走它,它只能用来建造真正的汽车,而且可以使用它制造很多汽车。那么如何创建对象呢?

首先要对类进行实例化,实例化是通过关键字new来声明一个对象。然后使用如下格式来调用要使用的方法:

在6.1节中已经讲过,类是一个抽象的描述,是功能相似的一组对象的集合。如果想用到类中的方法或变量,首先就要把它具体落实到一个实体,也就是对象上。

以SportObject类为例,实例化一个对象并调用playBasketball()方法。代码如下:

运行结果如下:

6.2.4 成员变量

类中的变量,也称为成员变量(也有称为属性或字段的)。成员变量用来保存信息数据,或与成员方法进行交互来实现某项功能。例如,在SportObject类中定义一个name(运动员姓名)成员变量,接下来就可以在playBasketball()方法中使用该变量完成某个功能。

定义成员变量的格式如下:

说明

关键字可以使用public、private、protected、static和final中的任意一个。在6.2.9节之前,所有的实例都使用public来修饰。对于关键字的使用,将在6.2.9节中进行介绍。

访问成员变量和访问成员方法是一样的。只要把成员方法换成成员变量即可,格式如下:

【例6.01】 以图6.1和图6.2中描述的类和类的实例化为例,将其通过代码实现。首先定义运动类SportObject,声明3个成员变量$name、$height、$weight。然后定义一个成员方法playFootball(),用于判断申请的运动员是否适合这个运动项目。最后,实例化类,通过实例化返回对象调用指定的方法,根据运动员填写的参数,判断申请是否符合要求。代码如下:(实例位置:资源包\源码\06\6.01)

运行结果如图6.4所示。

图6.4 实例化类运行效果

说明

“$this->”作用是调用本类中的成员变量或成员方法,这里只要知道含义即可。在6.2.8 节中将介绍相关的知识。

注意

无论是使用“$this->”还是使用“对象名->”的格式,后面的变量是没有$符号的,如$this-??> beatBasketBall、$sport->beatBasketBall。

6.2.5 类常量

既然有变量,当然也会有常量。常量就是不会改变的量,是一个恒值。圆周率是众所周知的一个常量。定义常量使用关键字const,如:

例如,先声明一个常量,再声明一个变量,实例化对象后分别输出两个值。代码如下:

运行结果如下:

可以发现,常量的输出和变量的输出是不一样的。常量不需要实例化对象,直接由“类名+常量名”调用即可。常量输出的格式如下:

说明

类名和常量名之间的两个冒号“::”称为作用域操作符,使用这个操作符可以在不创建对象的情况下调用类中的常量、变量和方法。关于作用域操作符,将在6.2.8节中进行介绍。

6.2.6 构造方法和析构方法
1.构造方法

当一个类实例化一个对象时,可能会随着对象初始化一些成员变量。

说明

初始化表示“开始时做好准备”。在软件开发中对某个东西初始化时,就是把它设置成一种我们期望的形状或条件,以备使用。

如例6.01中的SportObject类,现在再添加一些成员变量,类的形式如下:

实例化一个SportObject类的对象,并对这个类的一些成员变量赋初值。代码如下:

可以看到,如果赋初值比较多,写起来就比较麻烦。为此,PHP引入了构造方法。构造方法是生成对象时自动执行的成员方法,作用就是初始化对象。该方法可以没有参数,也可以有多个参数。构造方法的格式如下:

注意

函数中的“__”是两条下画线“_”。

例如,重写了SportObject类和playFootBall()方法,下面通过具体实例查看重写后的对象在使用上有哪些不一样。代码如下:

运行结果如下:

可以看到,重写后的类,在实例化对象时只需一条语句即可完成赋值。

说明

构造方法是初始化对象时使用的。如果类中没有构造方法,那么PHP会自动生成。自动生成的构造方法没有任何参数,没有任何操作。

2.析构方法

析构方法的作用和构造方法正好相反,是在对象被销毁时调用,作用是释放内存。析构方法的格式如下:

例如,首先声明一个对象$sport,然后再销毁对象。可以看出,使用析构方法十分简单。代码如下:

运行结果如下:

说明

PHP使用的是一种“垃圾回收”机制,自动清除不再使用的对象,释放内存。也就是说即使不使用unset()函数,析构方法也会自动被调用,这里只是明确一下析构方法在何时被调用。一般情况下是不需要手动创建析构方法的。

6.2.7 继承和多态

继承和多态最根本的作用就是完成代码的重用。下面就来介绍PHP的继承和多态。

1.继承

子类可以继承父类的所有成员变量和方法,包括构造方法。当子类被创建时,PHP会先在子类中查找构造方法。如果子类有自己的构造方法,PHP会先调用子类中的方法。当子类中没有时,PHP则去调用父类中的构造方法,这就是继承。

例如,在6.1节中通过图片展示了一个运动类,在这个运动类中包含很多个方法,代表不同的体育项目,各种体育项目的方法中有公共的属性。例如,姓名、性别、年龄……但还会有许多不同之处,例如,篮球对身高的要求、举重对体重的要求……如果都由一个SportObject类来生成各个对象,除了那些公共属性外,其他属性和方法则需自己手动来写,工作效率得不到提高。这时,可以使用面向对象中的继承来解决这个难题。

下面来看如何通过PHP中的继承来解决上述问题。继承是通过关键字extends来声明的,继承的格式如下:

说明

subClass为子类名称,superClass为父类名称。

【例6.02】 使用SportObject类生成了两个子类:PlayBasketBall和WeightLifting,两个子类使用不同的构造方法实例化了两个对象Playbasketball和weightlifting,并输出信息。代码如下:(实例位置:资源包\源码\06\6.02)

运行结果如图6.5所示。

图6.5 继承父类运行结果

2.多态

多态好比有一个成员方法让大家去游泳,这个时候有的人带游泳圈,还有人拿浮板,还有人什么也不带。虽是同一种方法,却产生了不同的形态,这就是多态。

例如,定义一个汽车抽象类Car,它有一个获取速度的成员方法getSpeed()。现在有3个汽车品牌的子类,分别继承Car父类,并且都有一个获取速度的成员方法getSpeed()。3个不同子类,调用同一个方法,将产生3种不同的形态,代码如下:

运行结果如下:

6.2.8 “$this->”和“::”的使用

通过例6.02可以发现,子类不仅可以调用自己的变量和方法,也可以调用父类中的变量和方法。那么对于其他不相关的类成员同样可以调用。

PHP是通过伪变量“$this->”和作用域操作符“::”来实现这些功能的,这两个符号在前面的学习中都有过简单的介绍。本节将详细讲解两者的使用。

1.$this->

在6.2.3节中,对如何调用成员方法有了基本的了解,即使用对象名加方法名,格式为“对象名->方法名”。但在定义类时(如SportObject类),根本无法得知对象的名称是什么。这时如果想调用类中的方法,就要用伪变量“$this ->”。“$this”的意思就是本身,所以“$this->”只可以在类的内部使用。

例如,当类被实例化后,“$this”同时被实例化为本类的对象,这时对“$this”使用get_class()函数,将返回本类的类名。代码如下:

运行结果如下:

说明

get_class()函数返回对象所属类的名字,如果不是对象,则返回false。

2.操作符“::”

相比伪变量“$this”只能在类的内部使用,操作符“::”更为强大。操作符“::”可以在没有声明任何实例的情况下访问类中的成员方法或成员变量。使用“::”操作符的通用格式如下:

这里的关键字分为以下3种情况。

parent关键字:可以调用父类中的成员变量、成员方法和常量。

self关键字:可以调用当前类中的静态成员和常量。

类名:可以调用本类中的变量、常量和方法。

例如,依次使用了类名、parent关键字和self关键字来调用变量和方法。读者可以观察输出的结果。代码如下:

运行结果如下:

说明

关于静态变量(方法)的声明及使用可参考6.2.10节相关内容。

6.2.9 数据隐藏

细心的读者看到这里,一定会有一个疑问:面向对象编程的特点之一是封装性,即数据隐藏。但是在前面的学习中并没有突出这一点。对象中的所有变量和方法可以随意调用,甚至不用实例化也可以使用类中的方法、变量。这就是面向对象吗?

这当然不算是真正的面向对象。如果读者是从本章第一节来开始学习的,一定还会记得在6.2.4节讲成员变量时所提到的那几个关键字:public、private、protected、static和final。这就是用来限定类成员(包括变量和方法)的访问权限的。本节先来学习前3个。

说明

成员变量和成员方法在关键字的使用上都是一样的。这里只以成员变量为例说明几种关键字的不同用法。对于成员方法同样适用。

1.public(公共成员)

顾名思义,就是可以公开的、没有必要隐藏的数据信息。可以在程序中的任何位置(类内、类外)被其他的类和对象调用。子类可以继承和使用父类中所有的公共成员。

在本章的前半部分,所有的变量都被声明为public,而所有的方法在默认状态下也是public。所以对变量和方法的调用显得十分混乱。为了解决这个问题,就需要使用第二个关键字private。

2.private(私有成员)

被private关键字修饰的变量和方法,只能在所属类的内部被调用和修改,不可以在类外被访问。在子类中也不可以。

例如,对私有变量$name的修改与访问,只能通过调用成员方法来实现。如果直接调用私有变量,将会发生错误。代码如下:

运行结果如图6.6所示。

图6.6 private关键字

说明

对于成员方法,如果没有写关键字,那么默认就是public。从本节开始,以后所有的方法及变量都会带上关键字,这是一种良好的书写习惯。

3.protected(保护成员)

private关键字可以将数据完全隐藏起来,除了在本类外,其他地方都不可以调用,子类也不可以。对于有些变量希望子类能够调用,但对另外的类来说,还要做到封装。这时,就可以使用protected。

说明

被protected修饰的类成员,可以在本类和子类中被调用,其他地方则不可以被调用。

例如,声明一个protected变量,然后使用子类中的方法调用一次,最后在类外直接调用一次,观察一下运行结果。代码如下:

运行结果如图6.7所示。

图6.7 protected关键字运行结果

说明

虽然PHP中没有对修饰变量的关键字做强制性的规定和要求,但从面向对象的特征和设计方面考虑,一般使用private或protected关键字来修饰变量,以防止变量在类外被直接修改和调用。

6.2.10 静态变量(方法)

不是所有的变量(方法)都需要通过创建对象来调用。可以通过给变量(方法)加上static关键字来直接调用。调用静态成员的格式如下:

关键字可以是:

self,在类内部调用静态成员时所使用。

静态成员所在的类名,在类外调用类内部的静态成员时所使用。

注意

在静态方法中,只能调用静态变量,而不能调用普通变量,而普通方法则可以调用静态变量。

使用静态成员,除了可以不需要实例化对象,另一个作用就是在对象被销毁后,仍然保存被修改的静态数据,以便下次继续使用。这个概念比较抽象,下面结合一个实例说明。

首先声明一个静态变量$num,声明一个方法,在方法的内部调用静态变量,然后给变量加1。依次实例化这个类的两个对象,并输出方法。可以发现两个对象中的方法返回的结果有了一些联系。直接使用类名输出静态变量,看有什么效果。代码如下:

运行结果如下:

如果将程序代码中的静态变量改为普通变量,如“private $num=0;”,那么结果就不一样了。读者可以动手试一试。

说明

静态成员不用实例化对象,当类第一次被加载时就已经分配了内存空间,所以直接调用静态成员的速度要快一些。但如果静态成员声明得过多,空间一直被占用,反而会影响系统的功能。这个尺度只能通过实践积累,才能真正地掌握。

6.3 小结

本章主要介绍了面向对象的概念、特点和面向对象的应用。虽然本章关于面向对象概念介绍得很全面、很详细,但要想真正明白面向对象思想,必须要多动手实践,多动脑思考,注意平时积累等。希望读者通过自己的努力能有所突破。

6.4 实战

6.4.1 调用类的成员方法

实例位置:资源包\源码\06\实战\01

试着创建一个商品类Goods,声明一个成员变量$ids(商品id数组)。然后定义一个成员方法searchGoods(),用于查找某个商品id是否存在于商品数组$ids中。运行结果如图6.8所示。

图6.8 实例运行结果

6.4.2 生成图片验证码

实例位置:资源包\源码\06\实战\02

试着创建ValidateCode类,用于生成图片验证码,运行结果如图6.9所示。

图6.9 生成图片验证码