这次辅导主要是对类的多态性进行讲解。由于PHP是弱类型语言,所以导致在有些特征上有与Java等语言不同的地方,但总体上是差不多的。
1、实现类多态的三种途径
类的多态,就是用基类(父类)去管理派生类(子类),这些父类与派生类具有一致的行为,当父类对象指向子类对象的时候,具有的是子类的行为。
可以通过以下三种方式实现类的多态:
(1)重写父类方法
(2)使用抽象类
(3)实现接口
2、重写父类方法
从父类继承,你会得到父类的所有属性和方法,只不过受保护级别限制,public成员,你可以使用,别人也可以访问;protected成员,你自己和自己的子类可以访问,别人不能访问;private成员,你看不到,也使用不了(这一点有分歧,有人认为私有属性和方法不继承)。
在子类中,重新定义一个与父类同名的方法,称为方法重写。
方法重写时的注意事项:
(1)方法名与参数个数必须相同。参数不一致,会有warning提示。这一点与Java稍有不同。
(2)重写的方法的保护级别必须小于等于父类的方法。父类方法是public的,重写方法可以public、protected、private都可以;父类方法是protected,重写方法可以是protected和private。父类方法是private的,那子类可以定义一个新方法(这个方法正好与父类方法同名)。
(3)如果类的方法被声明为final,那这个方法不能被重写。如果类被声明为final,那这个类不能被继承了。
子类调用父类成员使用范围运算符:parent::成员名,前提是保护级别允许(public或protected)。
3、这真的是多态吗?
上面的foreach循环中,$shape是什么类型的变量? 在循环中加个调试语句试一下:
var_dump($shape);
会发现$shape变量分别是Point、Circle、Rectangle、Cylinder和Cube类型对象。主要是因为PHP是弱类型,它是通过赋值的数据推断类型的。
这意味着,如果不使用继承来完成这些类,只要保证这些类都有同样的getArea()和getVolume()方法,也可以实现这个功能。Java这类强类型不行,因为每个变量要有明确的类型,所以只能通过基类去管理派生类。
但你如何保证呢?从另一种形式说,通过继承和方法重写(即多态),你可以保证一组类具有相同的行为,从这一点说,这也是多态。
4、instanceof运算符
instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例。
子类属于父类的实例,所以如果 $c是Circle类对象,$c instacneof Point的结果是true。
instanceof也可以用来判断类对象是否实现了某个接口。
6、抽象类
抽象类用abstract定义。类中只要有一个方法是抽象方法,这个类就必须定义为抽象的。如果全是实体方法,这个类也可以定义为抽象类。
抽象类只能由子类来继承,不能用来实例化对象。因为不能实例化,所以没有构造函数与析构函数。
子类从抽象类继承,必须实现(重写)所有抽象方法。其它实体方法可以重写也可以不重写,没有重写就继承过来。
子类实现了抽象方法就是实体方法了,它的子类可以继承过来,可以重写也可以不重写。
使用抽象类,主要是因为有些方法不好定义具体的行为,就交给子类来定义的,同时也保证子类具有一致的行为。
方法重写规则与前面一致。
7、接口
接口使用interface定义。同类一样,不定义访问属性,都是public。
接口名称建议是i开头,如iCountable。
接口中可以定义方法和常量,都必须是public的。
接口中定义的所有方法都是空方法(只是说明没有实现)。
接口中可以定义构造函数。(工厂模式时有用,与Java不同)
一个类要实现接口,使用implements关键字。一个类只能从一个父类继承,但可以实现多个接口。
实现接口的类,必须实现接口中所有方法(不然会有致命错误Fatal error),并且实现时,方法的定义形式要与接口中的定义一致(方法名与参数名称均要完全相同)。
接口也可继承自一个或多个接口,使用extends关键字。
PHP没有完全解决好接口中的名称冲突问题。如果一个类实现多个接口,这些接口中可以有同名的方法,但这些方法的定义必须签名相同。
接口中可以定义常量。常量使用方法与类的常量相同,后面讲。
8、类的静态成员
类中可以定义静态字段和静态方法,使用static关键字。
在类的内部,使用self关键字来引用,如:self::$count
在类的外部,使用类名来引用,如:Student::getCount()
9、理解浅复制
将一个类的对象(已经用new 实例化)赋值给一个变量,得到的是这个对象的引用(两个变量指向同一个对象),不管有没有用&运算符。
使用clone运算符,可以将对象复制一份给变量(可以说是2个对象了)。但是如果这个对象里面的有字段是其它类的对象(比如学生对象里面有课程字段,它是Course类的对象),这些字段克隆过去仍然是引用(浅复制)。除非你去定义__Clone魔术函数(自己决定这些字段如何复制过去)。
10、类的常量
可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。使用const关键字来定义(不是使用define函数)。
在类的内部,使用self关键字来引用,如:self::constant
在类的外部,使用类名来引用,如:MyClass::constant
11、trait简介
trait是PHP提供的一种代码复用的方法,他主要用于类的组合(接口也是组合的一种方式,但接口中只有抽象方法)。
trait的定义与class一样,除了关键字是使用trait而不是class。
trait不能用来实例化对象,只能在定义类时,通过use语句引用到类中。(个人感觉没什么用,仅仅是方便程序员复用代码)。
可以这么理解,继承是纵向组合代码,trait是横向组合代码。
12、类型约束:方法的参数可以指定类型
看下面的例子:
为函数ShowArea指定了参数必须为Point类型,那么,Point类的子类都可以作为参数传递给它,结果是:
但Student类对象不能传递给它,会有错误:
类类型、接口、数组、Callable(函数)可以作为类型约束,但标量类型(如int,string)不能作为类型约束(因为能自动进行类型转换)。
类中的方法也支持类型约束。
13、练习一下
将我们在Java中学习的,使用抽象类和接口方式实现的多态性演示,在PHP中实现一下。