PHP父类与子类

在类的“继承”关系中,父类与子类是两个最基本的概念,我们先来看一下生活中继承相关的概念。如下图。

PHP类与子类

本人非汽车专业人士,只为说明软件问题,不能作为科学依据哈!:)

在图中,我们可以看到,单讲“汽车”概念包括一些基本的特点,如都有动力系统、车门、轮子等。再看更具体一点的汽车类型,如轿车就比较传统,包括很多综合应用特性;SUV则突出高大的外形和实用的空间;跑车要有出色的动力和操控;卡车则是主要是为了运输。重要的是,这些车辆类型都有着“汽车”的基本特征。

那么,从面向对象编程的角度上看,我们就应该将“汽车”定义为一个基本的类,然后,可以通过继承来创建更具体的汽车类型;此时,“汽车”就是一个父类,而轿车、SUV、跑车、卡车等就是“汽车”的子类。

父类定义了一组共同的属性和方法,这些成员定义了子类的基本结构;子类则通过“继承”父类,获取基本的属性和方法,然后,子类在父类的基础之上,可以进行对父类的成员进行扩展或修改,以定义更加具体的属性和方法。

此外,在一些编程语言中,父类也称为基类。

在PHP中,我们定义两个类的继承关系,关键是在子类定义时使用extends关键字。extends的意思为扩展,但在传统的面向对象编程概念中,子类与父类的关系则称为继承(inherit),这只是名词上的区别,但应用上的本质是一样的。 如下面的代码,我们演示了基本的继承应用。

class CAuto  
{  
    public doorCount = 4;     publicwheelCount = 4;  
    public maxSpeed = 0;     public function drive()     {         echo "The Auto is running, Max Speed ",this->maxSpeed," km/h.";  
    }  
}  

class CCar extends CAuto  
{}  

car = new CCar();car->drive();

运行代码会显示“The Auto is running, Max Speed 0 km/h. ”,这是因为CCar类通过继承CAuto类默认拥有了它的所有成员,也就是说,CCar类实例实际调用的就是CAuto类中的drive()方法。

不过,这不科学呀,汽车的最大速度不应该是0呀!好的,我们修改一个CCar类的定义,如下面的代码。

class CCar extends CAuto  
{  
    public maxSpeed = 250;     public function drive()     {         echo "The Car is running, Max Speed ",this->maxSpeed," km/h.";  
    }  
}

再次执行代码,我们会看到“The Car is running, Max Speed 250 km/h. ”,是的,我们通过在CCar类中重写的drive()方法,实现了与父类中同名方法的不同行为。

这两个类的使用就是继承的最基本形式了,不过,关于继承还有很多主题需要我们关注,下面就一一讨论。

能否继承

前面,我们定义的CAuto类可以被CCar很愉快地继承,但是,有些类并不希望被其它的类继承的,当然,并不是这些类太小气,而是代码结构方面的设计要求所决定的,那么,对于不能继承的类,我们应该怎么定义呢?

在PHP中,我们在定义类时使用final关键字,那么,这个类就不能被继承,如下面的代码。

final class ClsA  
{}  

class ClsB extends ClsA  
{}

代码很简单,不过,它们是不能正确运行的。在定义ClsA类时,我们使用了final关键字,它将不能被继承;然后,我们定义ClsB时继承ClsA,此时,错误就产生了。

现在,我们知道,默认情况下,类是很慷慨的,它可以把自己的成员提供给它的子类使用,但有时候,某些类是需要一些个人主义的,那么,这个类的定义就应该使用final关键字。

不过,有时候,父类可能有些纠结,在它的成员中,有些可以让子类调用,而有些则不能;那么,这些成员应该如何区分呢?答案就是在定义类成员时使用可访问性关键字,如:

  • 父类中使用public或protected关键字定义的成员,可以被子类访问。
  • 父类中使用private关键字定义的成员,不能被子类访问。

成员扩展

前面,我们已经知道在子类定义时使用extends关键字,可以自动拥有父类的所有公共和受保护的成员,但是,我们也说过,在子类可能需要更多的成员来完成更具体的工作,此时,我们就可以在子类创建新的属性和方法,完成对父类功能的扩展。如下面的代码:

class ClsA  
{  
    public name = '';     public function work()     {         echothis->name,' is working.';  
    }  
}  

class ClsB extends ClsA  
{  
    public speed = 0;     public function run()     {         echothis->name,' is running, Speed is ',this->speed,'km/h.';     }  
}objB = new ClsB();  
objB->name = 'Tom';objB->speed = 10;  
objB->work();  
echo '<br>';objB->run();

代码中,我们首先定义了ClsA类,其中定义了name变量和work()方法,然后,我们创建了它的子类ClsB;在ClsB类中,我们又定义了speed变量和run()方法,这样,我们也就完成了子类对父类成员的扩展工作。运行代码结果如下图。

PHP类与子类

实际上,我们可以看到,对于类的功能扩展,实际上是非常简单的,我们只需要在子类定义新的成员就可以了。

重写父类成员

通过CAuto和CCar类的定义,我们也可以看到,在子类中,我们可以定义同名的成员,此时,子类中的新的成员就可以覆盖父类中的同名成员,再看一下下面的例子。

class ClsA  
{  
    public name = '';     public function work()     {         echothis->name,' is working.';  
    }  
}  

class ClsB extends ClsA  
{  
    public speed = 0;     public function work()     {         echothis->name,' is running. Speed is ',this->speed,'km/h.';     }  
}objB = new ClsB();  
objB->name = 'Tom';objB->speed = 10;  
$objB->work();

本例中的ClsA类与前面的示例是一样的,不同点在于ClsB类的定义,我们可以看到,在ClsB类中,我们定义了work()方法,此时,这个方法也就是覆盖了ClsA类中的同名方法,当我们调用ClsB实例的work()方法,结果如下图。

PHP类与子类

在子类中,如果我们使用同名成员覆盖了父类中的成员,并不意味着在子类中就不能使用父类成员了,不过,前提是父类的成员应该定义为public或protected。下面,我们修改ClsB类中的work()方法,如下面的代码。

class ClsB extends ClsA  
{  
    public speed = 0;     public function work()     {         parent::work();         echo '<br>';         echothis->name,' is running. Speed is ',$this->speed,'km/h.';  
    }  
}

请注意代码中的“parent::work();”语句,我们使用parent关键字来调用父类中的成员,这样,修改后的ClsB类中的work()方法就可以显示两个信息,如下图。

PHP类与子类

与关键字parent相对应的是self关键字,它代表本类,如self::work()。

在这一部分,我们方法演示了如何在子类中重写父类成员,而变量的重写方式是相同的;我们需要注意的是,在子类中重写父类成员时,只需要在子类中定义同名的成员就可以了,如果在子类中需要访问父类中的成员(定义为public或protected),可以使用parent关键字,而调用本类成员则可以使用self关键字。此外,parent和self关键字不但可以调用实例成员,同时也可以调用静态成员。

关于重写需要注意,当父类中的方法定义为终极版,也就是使用final关键字时,也是不能被子类重写的。当父类中的成员定义为静态时,在子类中重写也必须定义为静态成员,反之亦然。

继承关系中的对象类型判断

我们知道,使用instanceof运算符或is_a()函数,可以判断一个对象是不是一个类的实例,那么,当我们定义了类的继承关系时,会有什么样的情况出现呢?我们先看下面的代码。

class ClsA  
{}  

class ClsB extends ClsA  
{}  

objB = new ClsB();  
var_dump(objB instanceof ClsB);  // 显示bool(true)  
print '<br>';  
var_dump($objB instanceof ClsA);  // 显示bool(true)

我们将objB对象定义为ClsB类的实例,所以,第一个var_dump()函数会显示bool(true),这没什么问题;但是,第二个var_dump()函数中,我们使用instanceof运算符判断objB对象是否为ClsA类的实例,结果依然是true。

这实际上就是继承关系中的一个重要特点,当一个对象定义为子类的实例时,它同时也是父类的间接实例。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程