类的种类
PHP 7 中支持多种类(class
)的实现,包括普通类、抽象类、接口、特性(trait
)以及 final
类和匿名类。
普通类
类以关键字 “class”
定义,后跟类名。类名不能是 PHP
预留关键字,必须以字母或下划线开头,每个类有属于自己的常量、方法、属性。类的实例叫作对象,可通过关键字 “new”
来实例化对象。
先编写一个 PHP
的普通类:
<?php
class A
{
const B = 1;
static $b = 2;
var $c = 3;
public function b()
{
var_dump("public");
}
protected function c()
{
var_dump("protected");
}
private function _d()
{
var_dump("private");
}
static function e()
{
var_dump(self::$b, self::B);
//var_dump(self::$c); //不能通过“::”访问非静态的属性
}
}
$a = new A();
$a->b();
//$a->c(); //Fatal error: Uncaught Error: Call to protected method A::c() from context ''
//$a->d(); //Fatal error: Uncaught Error: Call to undefined method A::d()
A::e();
说明:
-
类的属性和方法有三个访问级别,分别为
public
(公有),protected
(受保护)或private
(私有)。类的外部不能直接调用protected
(受保护)或private
(私有)的方法和属性。 -
类的属性有普通属性和静态、常量属性,静态、常量属性则分别用关键字
“static”
和“const”
声明。 -
类的普通方法、属性被自己的成员函数调用时可使用
“$this->”
,静态方法及静态属性也可通过这种方式访问。 -
类的常量属性、静态属性、静态方法,通过
“self::”
调用。非静态的方法也可以通过“::”
调用,但是非静态的属性不能通过“::”
访问。
抽象类
PHP 5 开始支持抽象类和抽象方法,抽象类不能被实例化。
下面编写一个抽象类的示例:
<?php
abstract class A
{
abstract protected function b($name);
abstract protected function c();
// abstract private function c(); /*抽象方法不能设置为私有*/
abstract public function d();
/*PHP Fatal error: Abstract function A::d() cannot contain body
abstract public function d(){}
*/
public function f(){
echo 'A->d()';
}
}
class E extends A{
public function b($name, $name2=''){
echo $name.$name2;
}
public function c(){
echo 'pub_c';
}
public function d(){
echo 'pub_d';
}
/*访问级别不能降低*/
/*protected function d(){
echo 'pri_d';
} */
/*参数个数也必须一致*/
protected function b($name, $name2){
echo 'pri_d';
}
}
$obj = new E();
$obj->b('name');
$obj->c();
$obj->d();
说明:
-
抽象方法必须被子类继承实现,所以不能为私有,只能是受保护的或公有的;
-
抽象类的方法访问控制级别必须和子类相等或更为宽松。例如,父类的抽象方法是受保护的,子类实现时则必须为受保护的或者公有的;
-
抽象方法的实现,必传参数的数量及类型必须严格一致;
-
抽象类的非抽象方法,子类可不实现,等同于普通类方法的继承;
-
抽象类中的抽象方法,只能定义,不能实现其方法体;
-
抽象类可定义常量,且可被子类覆盖。
接口
PHP 与大多数面向对象编程语言一样,不支持多重继承,每个类只能继承一个父类。
对象接口(interface
)里定义的方法子类需全部实现,且接口不能直接被实例化。接口的主要特性和需注意的点如下。
-
接口类可以通过
extend
继承一个或多个接口类,多个接口之间用逗号分隔,用以实现接口类的扩充。 -
接口类定义的方法必须声明为公有,因此子类的实现方法也只能为公有。接口方法体也必须为空。
-
接口类定义的常量和类常量使用方式一样,但不能被子类或者子接口覆盖。
-
普通类通过关键字
“implements”
来实现一个或多个接口。 -
继承多个接口,方法不能有重名。
-
普通类继承接口,必须实现接口类里面所有的方法,参数也和接口方法定义相同。可加默认参数,这点和抽象类方法的实现基本一致。
接下来,结合 PHP
代码一起来看接口的定义与实现。
(1)接口定义
interface A{
public function demo1(); /* 接口方法不能有方法体,且只能为公有 */
/* PHP Fatal error: Access type for interface method A::demo1() must be omitted */
//protected function demo1();
}
(2)接口扩充
interface B{
public function demo2();
}
interface C extends A, B{/* 只能通过extends实现接口类扩充 */
public function demo3();
}
(3)普通类对接口的实现
class D implements A{
/* PHP Fatal error: Declaration of D::demo1 ($name) must be compatible with A::demo1 () */
//public function demo1($name){
public function demo1($name=1) {/* 接口未定义的参数,可通过加默认值实现 */
}
}
(4)普通类实现多个接口
class D implements A, B, C{/* 通过implements实现多个接口类,类之间通过逗号分隔 */
/* 继承的所有接口方法都必须实现,否则报错 */
public function demo1(){
}
public function demo2(){
}
public function demo3(){
}
}
特性
特性(trait
)从 PHP5.4.0 开始启用,便于在不同层次结构内实现代码复用。特性不能直接被实例化,主要特性和需注意的点如下。
-
特性与普通类相似,有自己的方法、属性等,但是不可通
extends
继承,也没有类常量。 -
特性的方法如果和当前类方法冲突,会被当前类的方法覆盖。如果和基类方法冲突,特性方法会覆盖基类中的方法。优先级:当前类>特性类>基类。
-
一个类加载了多个特性,当多个特性中方法有重名时,需要在代码中通过关键字
“insteadof”
设置优先级或者通过“as”
关键字重命名处理,否则报错。
结合 PHP
代码实践特性的定义与用法。
(1)特性的定义
trait A{ //定义特性A
public $b = 'public'; //定义特性的属性
function demo1(){ //定义特性的普通方法
echo 'A->demo1';
}
static function demo2(){ //定义特性的静态方法
echo 'A::demo2';
}
abstract public function demo3(); //定义特性的抽象方法
}
(2)特性扩容
trait B{
use A; //通过关键字use即可拥有特性A全部的方法属性
function demo4(){
echo 'B->demo4';
}
}
可见,此时 B
包含了 A
的所有内容,扩容非常简单。
(3)普通类使用特性
class ChildClass {
use B; /* 此时相当于把特性B所有方法打包加载进子类 */
function demo3(){ /* 和继承类似,必须实现特性中的抽象方法 */
echo 'trait:abstract:demo3';
}
}
(4)普通类加载多个特性
trait C{
function demo5(){
echo 'C->demo5';
}
}
(5)通过 use 加逗号加载多个特性
class ChildClass{
use B, C; /* 逗号隔开,加载多个特性;也可一个一个地加载 use B; use C; */
function demo3(){
echo 'trait:abstract:demo3';
}
}
(6)特性方法优先级(自身>特性>基类)
class ParentClass{
function demo1(){ /* 被特性A中demo1覆盖 */
echo 'Parent->demo1';
}
}
class ChildClass extends ParentClass{
use A;
function demo2(){ /* 自身类demoe2覆盖了特性A中的demo2 */
echo 'Child->demo2';
}
/* 避免重复代码,A中抽象方法demo3实现暂时忽略 */
}
$obj = new ChildClass();
$obj ->demo1(); /* A->demo1,特性A的demo1覆盖了基类的demo1 */
$obj ->demo2(); /* Child->demo2,自身类的demo2覆盖了特性A中demo2 */
(7)优先级冲突解决
当两个特性的方法相同时,需要通过 “insteadof”
关键字定义谁优先,或通过 “as”
关键字修改方法名。先定义一个方法与 A
冲突的特性 D
:
trait D{
function demo1(){
echo 'C->demo1';
}
}
普通类同时加载 A
与 D
:
class ChildClass {//extends ParentClass {
//use A, D; /* 方法重名,且没有自定义优先级,则报错:PHP Fatal error: Trait method demo1 has not been applied, because there are collisions with other trait methods*/
//通过insteadof人为地定义优先级,解决冲突,或者通过as给其中一个方法取个别名
use A, D {
D::demo1 insteadof A;
//D::demo1 AS dDemo1;
}
/* 避免重复代码,A中抽象方法实现暂时忽略 */
}
8)修改特性方法的访问控制级别。
在普通类中加载特性,也可通过 “as”
来修改其访问控制级别,如下示例代码:
class ChildClass {
use C {
demo5 as private;
}
}
final 类
如果不希望一个类被继承,可以使用 “final”
来修饰。如果一个方法不想被子类覆盖,也可以这样声明。
结合 PHP
示例来看。
(1)final
修饰的函数不能被覆盖
class A{
final function b(){
}
}
class B extends A {
function b(){//PHP Fatal error: Cannot override final method A::b()
echo 'B->b';
}
}
(2)final
修饰的类不能被继承
final class A{
function b(){
}
}
class B extends A {// PHP Fatal error: Class B may not inherit from final class (A)
}
类的属性不能定义为 |