面向对象高级
思考:面向对象也就是把函数和变量常量之类的多用了一层类包裹,然后多了个对象访问而已嘛,似乎也不像面向对象思想说的那样好啊?
引入:类和对象是面向对象的基础部分,是将基本思想使用代码方式设计简单实现。面向对象还有很多高级的部分,更能够体现面向对象思想编程的强大。
- 面向对象三大特性
- PHP继承
- 抽象类
- 接口
- 对象遍历
总结:面向对象核心的思想在于符合人类的思维逻辑,同时能够方便后期维护开发,实现代码的重复利用。这些都将在高级里面体现。
面向对象的三大特性
引入:在面向对象思想中,有三大特性:
封装
、继承
和多态
。思考:我们在使用类和对象来实现编程的时候,到底是遵循着什么样的逻辑呢?为什么要这样去规范类呢?
引入:面向对象很重要的思想就是隐藏,即外部不知道类内部是如何实现业务逻辑的,只管调用和使用结果,这个就是面向对象的三大特性之一:
封装
封装
定义:封装,字面意思就是将内容装到某个容器中,并进行密封保存。在面向对象思想中,封装指将
数据
和对数据的操作
捆绑到一起,形成对外界的隐蔽,同时对外提供可以操作的接口
。
- 数据:即要操作的数据,在面向对象的具体体现就是类成员属性(属性和静态属性)和类常量,这些都是在类的内部定义的用来保存数据的
- 数据的操作:即数据加工过程,在面向对象的具体体现就是方法,内部定义业务逻辑对数据进行加工处理。
- 捆绑到一起:即使用类结构{}将属性、类常量和方法存放到一起,成为一个整体
- 对外提供可操作的接口:即提供可以供外部访问的类成员(通常是方法)
//封装特性
class Saler{ //捆绑内容到一起,形成对外界隐蔽的整体
const ALLOW = true;
private static $count = 0;
private $money = 0; //数据:属性和类常量保存
public function getMoney(){ //数据操作
return $this->money;
}
}
//外部
$s = new Saler();
$s->getMoney(); //外部只能访问类中公有的方法,具体实现不可见
总结
- 封装是从对象抽象形成类的过程
- 封装是一种将数据和数据操作捆绑到一起,形成对外部的隐蔽,同时提供对外操作的接口
- 封装是面向对象第一大特性,也是面试官喜欢询问对面向对象思想理解的知识
继承
定义:继承,即有些类似的对象中,如人是一种大类,下面可以细分出一些小类,如男人、女人等,然后有一些公共的特性可以在一些基础类中体现,而其他相似或者较小类可以直接使用上级类中的公共代码。
- 继承的基础:子类(要继承其他类的类,也称之为派生类)与父类(被继承类,也称之为基类)之间本身是一种包含于被包含关系,如此才有可继承的前提
//大类
class Human{} //人类
//小类
class Man{}
class Woman{} //属于人类包含的部分,可以继承Human
- 继承关键字:extends,子类想要继承父类,则必须在子类结构申明时明确使用extends关键字来继承相关类
//父类
class Human{} //人类
//子类
class Man extends Human{}
class Woman extends Human{} //继承Human类
- 继承效果:子类可以不用自己去实现某些功能,而可以直接访问父类中已经存在的成员
//父类
class Human{
public function eat(){
echo '吃饭';
}
}
//子类
class Man extends Human{} //子类为空类:没有类成员
//实例化子类对象
$m = new Man();
$m->eat(); //输出吃饭
总结
- 继承extends是面向对象思想中实现代码重复利用的重要特性
- 继承是指子类可以直接访问父类中已经存在的成员
- 继承可以节省代码工作,同时允许子类中进行扩展,即在子类中增加必要的父类不存在的功能
多态
定义:多态,是指在发生类的继承的情况下,同时出现方法的重写(override),即子类拥有与父类同名的方法。然后在实例化对象的时候让父类对象指向子类对象(强制类型,PHP不支持),父类对象表现的子类对象的特点。
- 多态需要强类型语言,以Java为例
//父类
class Animal{
public void show(){
System.out.println("Animal");
}
}
//子类
class Dog extends Father{
//重写show方法
public void show(){
System.out.println("Dog");
}
}
//实例化:Java是强类型,必须指定保存数据的变量的类型
Animal a = new Dog(); //父类对象指向子类对象空间
a.show(); //打印Dog,父类对象调用的是子类方法
- PHP是弱类型语言,所以不存在变量的强制类型,因此PHP不支持多态。
总结
- 多态的发生必须是有继承关系,并且子类要重写父类方法
- 多态是指父类对象拥有子类形态,并且可以表现出子类的特性(调用子类方法)
- PHP是弱类型语言,不支持多态
继承
继承相关概念和实现
定义:继承extends,是指子类通过继承可以访问父类的成员。
- 继承基本语法:class 子类 extends 父类{}
//父类(基类)
class Human{}
//子类(派生类)
class Man extends Human{}
- 继承效果:父类被继承的内容,可以通过子类对象进行访问(只能是子类对象)
//父类(基类)
class Human{
public function showName(){
echo __CLASS__;
}
}
//子类(派生类)
class Man extends Human{}
//实例化子类对象:如果实例化父类对象Human,那么与子类和继承毫无关系
$m = new Man();
$m->showName(); //访问继承自父类的方法
- 继承目标:继承本质是针对同类有包含关系的共性继承,即父类通常是包含子类,子类属于父类。所以在父类中通常定义的是子类共有的一些特性成员,这是开发者默认遵循的规则
class Animal{}
class Man extends Animal{} //不会有语法错误,也可以继承,但是不符合实际
总结
- 继承是利用extends进行子类和父类的关联
- 继承利用extends关键字实现
- 继承是指实现继承关系后,子类对象可以访问父类被继承的成员。而父类对象不可以访问子类成员(没有关系)
有限继承
定义:有限继承,指子类在继承父类的成员的时候,并非继承所有内容,而是继承并使用父类部分内容。
- 继承内容:PHP中继承是子类继承父类所有的公有成员、受保护成员和私有属性,不能继承父类的私有方法
//父类
class Human{
const CALL = '人';
public $name = 'human';
protected $age = '100';
private $money = '100';
public function showName(){
echo $this->name;
}
protected function showAge(){
echo $this->age;
}
private function showMoney(){
echo $this->money;
}
}
//子类
class Man extends Human{}
//实例化子类
$m = new Man();
var_dump($m); //可以看到父类私有属性
$m->showName(); //允许直接访问:方法为公有允许类外访问
- 受保护继承protected,protected关键字的产生本身就是纯用于继承的,表示允许被子类在子类内部访问的意思,而不允许被外部直接访问。
//父类
class Human{
protected $age = '100';
protected function showAge(){
echo $this->age;
}
}
//子类
class Man extends Human{
//在子类内部增加公有访问访问继承自父类的受保护成员
public function getProtected(){
echo $this->age; //访问父类受保护属性
$this->showAge(); //访问父类受保护方法
}
}
//实例化子类对象
$m = new Man();
$m->getProtected(); //正确输出:说明可以访问
//$this中有public可读可直接用,protected可读,可调用, private可读,不能调用
- 访问父类私有成员:子类若想访问父类私有成员,那么前提是父类提供了能够访问私有成员的接口:即提供了公有或者受保护的方法给子类访问
//父类
class Human{
private $age = 100;
private $money = 100;
//提供接口供子类访问:此时通常是受保护的方法,肯定不允许外部直接访问的
protected function getAge(){
echo $this->age;
}
}
//子类
class Man extends Human{
//依然需要定义方法来访问继承自父类的受保护的成员
public function showPrivate(){
$this->getAge();
}
}
//实例化子类对象
$m = new Man();
$m->showPrivate(); //输出100,表示正确访问
注意:虽然子类可以通过以上方式来实现访问父类的私有成员(包括私有方法),但是从设计的意义上讲,私有就是不允许外部访问,所以父类通常不会提供对外的访问接口,以上案例只是为了知识说明。
- 静态成员(类常量)也遵循继承规则(PHP继承本质是对象),只是访问方式是由类进行访问
class Human{
const NAME = '人';
public static $count = 0;
protected static $type = array('黑','黄','白');
public static function getCount(){
echo self::NAME;
echo self::$count;
}
protected static function getType(){
print_r(self::$type);
}
}
class Man extends Human{
//依然需要子类中访问受保护成员
public static function getHuman(){
Human::getType();
}
}
echo Man::$count; //允许直接访问
Man::getCount(); //访问父类静态方法
Man::getHuman(); //利用子类公有方法访问父类受保护成员
- 构造方法和析构方法也可以被子类继承,此时需要注意子类对象实例化时对应的父类构造方法的参数
//父类
class Human{
private $money;
public function __construct($money){
$this->money = $money;
}
public function __destruct(){
echo 'die';
}
}
//子类继承
class Man extends Human{}
//子类实例化:自身是空类,没有指定构造方法
//$m = new Man(); //错误:缺少参数,因为会自动调用父类构造方法
$m = new Man(100); //正确
总结
- 继承是有限继承,理论上是用来继承父类允许被继承的部分,即使用public或者protected修饰的成员
- 因为对象的属性是保存在对象内存空间,所以父类的私有属性也会继承
- 父类私有成员本质不允许被子类访问,但是可以通过父类开放接口实现(一般不会这么操作)
- 静态成员也可以遵循继承规则
- 构造方法也可以被继承,因此在实例化子类对象的时候,要考虑到父类构造方法所使用到的参数问题
重写Override
定义:重写,即子类中定义了与父类重名的成员,子类可以重写父类任意类成员,通常重写是用来重写父类的方法,用于扩展或者更改某些业务逻辑。
- 子类继承父类,同时子类定义与父类同名的类成员
//父类
class Human{
public $name = 'Human';
public function show(){
echo __CLASS__,'<br/>';
}
}
//子类继承
class Man extends Human{
//定义同名属性
public $name = 'Man';
//定义父类同名方法
public function show(){
echo __CLASS__,' hello world<br/>';
}
}
- 重写父类成员之后,子类只会直接访问子类的成员(覆盖)
//接上述代码
//实例化子类对象
$m = new Man();
$m->show(); //输出Human hello world
var_dump($m); //只有子类属性$name
注意:不管是公有和是受保护属性,一旦重写,父类的就会不存在,而私有属性不会被覆盖而丢失
- 重写的要求1:子类重写父类的方法,控制权不能高于父类,即子类可以比父类更开放
//父类
class Human{
protected function show(){
echo __CLASS__,'<br/>';
}
}
//子类继承
class Man extends Human{
//重写
protected function show(){} //正确
public function show(){} //允许
private function show(){} //错误:控制权比父类更严格
}
- 重写的要求2:PHP中重写要求子类重写父类方法的时候,必须保证与父类同名方法参数一致
//父类
class Human{
protected function show(){
echo __CLASS__,'<br/>';
}
}
//子类继承
class Man extends Human{
//重写
public function show(){}
public function show($a){} //错误,与父类同名方法不一致
}
注意:在方法参数一致不单单要求数量一致,而且数据类型要求也必须相同,但形参名字可以不同;另外,在PHP7以前重写对于参数这块没有要求。
- 重写的要求3:重写针对的是被继承的成员,父类私有方法不会被继承,因此不受要求2规定
//父类
class Human{
private function show(){
echo __CLASS__,'<br/>';
}
}
//子类
class Man extends Human{
private function show($name){ //不会报错,因为本质不存在重写(父类Human::show没有被继承)
echo $name,'<br/>';
}
}
parent
- 重写是指子类拥有特殊的情况,一般是需要在父类的基础上进行扩展,此时如果想要继续保证父类被重写的方法继续执行(默认永远只访问子类重写的新方法),需要在子类重写方法的时候使用parent关键字
//父类
class Human{
protected function show(){
echo __CLASS__,'<br/>';
}
}
//子类继承
class Man extends Human{
//重写
public function show(){
//强制调用父类被重写方法
parent::show();
//扩展业务逻辑
echo __CLASS__,'<br/>';
}
}
注意:parent不能访问父类的属性,可以访问静态属性、静态方法、类常量和普通方法
总结
- 重写override是一种在子类中定义父类同名成员的操作
- 公有、受保护的属性重写是直接覆盖父类成员,私有属性不会被覆盖;公有、收保护的方法会被重写,但是私有方法不会被重写(私有方法本质没有被继承)
- 重写的要求
- 子类控制权不能高于父类控制权
- PHP7中要求被重写的方法必须与父类保持参数一致(数量和类型)
- 方法被重写后,访问调用的都是子类方法,如果想要访问父类方法,可以通过在子类方法中使用parent关键字来强制访问父类方法
- parent不能用于访问父类的属性(静态属性可以)
PHP继承特点
定义:PHP继承与其他纯面向对象(从设计之初就完全由面向对象思维支配)编程语言是有一些不一样的。
- PHP中继承只能单继承:即子类只有一个父类(有些语言支持多继承)
class Man{}
class Woman{}
class Ladyboy extends Man,Woman{} //PHP中错误,不允许继承多个父类
- PHP若想继承多个类,可以使用链式继承
class Man{}
class Woman extends Man{}
class Ladyboy extends Woman{} //Ladyboy包含了Man和Woman类中所有可继承的成员
- PHP中继承只有私有方法不能被继承
- PHP允许子类继承父类的构造方法和析构方法
总结:PHP中的继承与传统的面向对象继承有着一些小区别,大家在使用继承的时候要严格遵守PHP的继承规则
静态延迟绑定
定义:静态延迟绑定,即在类内部用来代表类本身的关键字部分不是在类编译时固定好,而是当方法被访问时动态的选择来访者所属的类。静态延迟绑定就是利用
static
关键字代替静态绑定self,静态延迟绑定需要使用到静态成员的重写
- 静态延迟绑定:使用static关键字代替self进行类成员访问
//父类
class Human{
public static $name = 'Human';
public static function showName(){
//静态绑定
echo self::$name,'<br/>';
//静态延迟绑定
echo static::$name,'<br/>';
}
}
//子类
class Man extends Human{
//重写父类静态属性
public static $name = 'Man'; //静态属性因为存储在类内部,因此不会覆盖
}
//子类访问
Man::showName(); //输出Human和Man
- 静态延迟绑定一定是通过继承后的子类来进行访问才有效果
//接上述代码
Human::showName(); //输出Human和Human(此时没子类的事儿)
总结
- 静态延迟绑定是指通过static关键字进行类静态成员的访问,是指在被访问时才决定到底使用哪个类
- 静态延迟绑定对比的是静态绑定self
- 静态延迟绑定的意义是用来保证访问的静态成员是根据调用类的不同而选择不同的表现
最终类Final
定义:最终类,使用final关键字修饰类名,表示此类不可以被继承。
- 基本语法:final class 类名
//最终类
final class Man{}
- 最终类无法被继承
//最终类
final class Man{}
class Man18 extends Man{} //致命错误:无法从final类继承
- final关键字不止修饰类表示类不可被继承,还能修饰方法,表示方法不能被重写
//父类
class Human{
public function show(){} //普通方法
public final function walk(){} //最终方法
}
//子类
class Man extends Human{
//重写
public function show(){} //没问题
public function walk(){} //致命错误:不能重写父类中的最终方法
}
总结
- final关键字修饰的类表示无法被继承
- final关键字还可以修饰方法,表示方法不能子类重写(通常类不会使用final关键字)
- final修饰类表示不希望类再出现子类,可以很好保护类的内部结构不被暴露
- final修饰方法表示不希望方法被修改,可以在一个更高的维度来保证同类事务的共同表现
抽象类Abstract
定义:抽象类,使用abstract关键字修饰的类,表示该类只能被继承,不能被实例化
- 基本语法:使用abstract关键字修饰类
//抽象类
abstract class Human{}
- 抽象类无法被实例化
//抽象类
abstract class Human{}
$h = new Human(); //致命错误,抽象类不能被实例化
- 抽象类只能被继承
//抽象类(父类)
abstract class Human{}
//子类
class Man extends Human{} //正确
- abstract关键字还可以用来修饰方法(抽象方法),abstract修饰的方法不能有方法体,而且有抽象方法的类必须声明为抽象类
//抽象方法抽象类
abstract class Human{
//定义抽象方法:没有方法体
abstract public function eat();
public function show(){} //普通方法有方法体
}
- 抽象方法因为要被子类继承实现,所以不能使用private修饰(私有方法不会被继承)
//抽象类
abstract class Human{
//抽象方法
abstract private function eat(); //错误:抽象方法不能私有化
}
- 子类继承抽象类后,如果抽象类中有抽象方法,那么子类必须选择自己成为抽象类或者实现抽象方法(所有抽象方法)
//抽象方法抽象类(父类)
abstract class Human{
//定义抽象方法:没有方法体
abstract public function eat();
public function show(){} //普通方法有方法体
}
//子类1:抽象类继承抽象类
abstract class Man extends Human{} //正常继承
//子类2:子类实现父类所有抽象方法
class Boy extends Man{
//实现从祖父类继承的eat抽象方法
public function eat(){
echo 'eat';
}
}
总结
- 使用abstract修饰的类叫做抽象类
- 抽象类不可以被实例化,只能被继承
- 因为抽象类无法被实例化,因此私有成员在类中没有实质意义(还需要额外提供受保护或者公有方法来实现访问)
- 抽象类的目的是用来规范子类(通常必配抽象方法)
- abstract还可以修饰方法,称之为抽象方法:抽象方法所在的类必须是抽象类,抽象方法不能有方法体
- 有抽象方法的抽象类被继承时子类要么自身是抽象类,要么实现所有抽象方法
- 抽象类这种结构管理,需要耗费较多的架构和初始代码,通常在比较大型或者规范的项目中才会使用