面向对象编程

PHP中的编程方式只有两种面向过程面向对象
OOP(Object Oriented Programming,面向对象程序设计)是一种高级的计算机编程架构。让我们更好的组织项目中的代码,目前所有流行的PHP框架都是面向对象的方式编写的,熟练掌握面向对象是PHP开发者的必备技能。

面向对象中的概念比较多,这里列出来的只是一小部分
以下需熟练掌握:

  • 类和对象
  • 命名空间
  • 类的自动加载
  • 魔术方法
  • 静态成员
  • 继承

    类和对象

    ( Class ) 是 面 向 对 象 程 序 设 计 ( OOP , Object Oriented Programming)实现信息封装的基础。类是一种用户定义类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象

面向对象编程有三大特性:封装继承多态。其中封装的意思就是说所有的代码必须要写在一个类中,不能把代码写到类的外部。不过因为 PHP 并不是一个纯粹的面向对象的语言,PHP 中即支持面向过程的语法又支持面向对象的语法,所以在 PHP 中即使把代码写到类的外部也是可以的。

定义类的方法

1
2
3
4
5
6
class{
#类由属性和方法构成
//常量
//属性
//方法
}

说明:

  • 一般类名的首字母大写,比如class Blog
  • 一般一个类写在一个文件中,文件名和类名相同,比如Blog.php文件中定义class blog
  • 在类中只包含,常量,属性。
    访问类型
    属性和方法包含三种访问类型public protected private
  • public (公有的、默认的):所有的内部成员或外部成员都可以访问(读和写)这个类成员(包括成员属性和成员方法)
  • private (私有的):被定义为private的成员,对于同一个类里的所有成员是可见的,即没有访问限制;但对于该类的外部代码是不允许改变甚至读操作,对于该类的子类,也不能访问private修饰的成员
  • protected(受保护的):被修饰为protected的成员不能被该类的外部代码访问。但是对于该类的子类有访问权限,可以进行属性、方法的读及写操作,该子类的外部代码包括其的子类都不具有访问其属性和方法的权限。

对象

对象是对类的实例化

类只是描述了要实现的功能,类中的代码要想执行,需要实例化出对象来,通过对象我们才能真正执行类中的代码。

类就相当于图纸定义了类的功能,对象是根据图纸制作出来的具体的实体。一个类可以创建出任意多个对象:

类的实例化:

1
2
new 类名;
#例 $blog = new Blog;

例化类的对象之后,我们就可以使用对象来访问类中的属性和方法了。

说明:

  • 在其他文件中如想使用类,首先需要先引入类文件
  • 类中的属性和方法都要使用 $对象-> 访问
  • 类中的 常量 和 静态成员 直接使用 类名:: 来访问

注意:一个类能创建多个对象,一个对象只能是一个类创建的

$this

在类的外部我们使用 -> 来访问类中的属性和方法:

1
$blog->title='haha'

那么,在类的内部应该如何访问类中的属性和方法呢?
使用$this

在类中如何访问常量和静态成员呢?
使用self::

命名空间

我们开实际大型项目时,我们可能需要引入很多第三方的类库,那么这些类库中有没有可能出现同名的类呢?肯定会有!
为了解决类同名的问题,PHP 中引入了 ”命名空间“ 机制。

namespace

我们可以使用namespace声明一个命名空间,然后在它后面定义的类就都属于这个命名空间,比如,我们声明一个叫做 test 的命名空间,并在空间中定义一个类:

1
2
3
4
namespace text;
class Student{
....
}

注意:namespace必须是文件中的第一行代码。
这个 Student 类现在就属于 test 这个命名空间中,这时当我们再要使用这个类时,必须要在前面加上命名空间:

1
$student = new test\Student;

注意:命名空间使用 \ 符号。
这样不同类包中的同名的类就不会冲突了,因为不同类包的命名空间都不相同,比如:腾讯公司的 Log 可能是属于 Tencent 命名空间下,阿里公司的类包可能是属于 Ali 命名空间下,即使它们的类名都叫 Log 也不会冲突:

1
2
3
4
5
// 实例化阿里 Log 类
$log = new Ali\Log;

// 实例化腾讯 Log 类
$log1 = new Tencent\Log;

这就是命名空间的用途。

命名空间也可以有子命名空间:

1
2
3
4
5
<?php
// 三级命名空间
namespace Ali\Domain\Beijing;

class Log { ... }

在实际工作当中,我们一般声明命名空间和类文件所在的目录相同。
比如:在 app/controllers 目录下有一个 BlogController 类:
app/controllers/BlogController.php

1
2
3
4
5
6
<?php
namespace app\controllers;

class BlogController
{
}

总结
总结:

  1. 为了避免类同名,所以引入命名空间
  2. 命名空间和类文件所在目录相同
  3. namespace 必须是文件中的第一行代码

use

现在每次实例化一个类时,需要写很长的类名(包含命名空间),如何能简化呢?
方法一:在同一个命名空间下,可以省略命名空间
方法二:使用 use
同一命名空间下
在使用同一个命名空间里面的类时,可以省略命名空间,直接写类名。

比如,我们再创建一个 models/User 类:

models/User.php

1
2
3
4
5
6
<?php
namespace app\models;

class User
{
}

因为这个类和同目录下的 Blog 类都在同一个命名空间 app\models 下,所以可以省略命名空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace app\models;

class User
{
public function getBlog()
{
// 引入类文件
require('./Blog.php');

$blog = new Blog;
}
}

总结:同一命名空间下的类,在使用时可以省略命名空间,但记住还是需要引入类文件的。
use
当使用不同命名空间中的类时,需要在实例化时加上命名空间,这会导致需要输入的类名比较长:

1
$blog = app\controllers\BlogController

如果想要简化可以使用 use 语句。

use 的功能是在文件的开始引入一个命名空间下的类,引入之后就可以直接使用类名来使用这个类了:

1
2
3
use app\controllers\BlogController;

$blog = new BlogController;

在引入时也可以为类起别名:

1
2
3
use app\controllers\BlogController as BC;

$blog = new BC;

示例、创建 testUse.php 文件

1
2
3
4
5
6
7
8
9
10
11
12
<?php
require('app/controllers/BlogController.php');
require('app/models/Blog.php');

use app\controller\BlogController as BC;
use app\models\Blog;

$blog1 = new BC;
$blog2 = new BC;
$blog3 = new BC;

$blogModel = new Blog;

类文件的自动加载

有没有发现我们每次在使用一个类时都要先使用 require 引入类文件,在一个大的项目中我们需要使用数十个类,如果每次都要引入数十个类文件,实在是太麻烦了。

为了解决这个问题,PHP 为我们提供了类的自动加载机制,有了这个机制,我们就可以直接使用类了,然后 PHP 会自动引入相应的类文件,我们就不用一个一个的手动引入了。

spl_autoload_register

PHP 中提供了一个 spl_autoload_register 函数,该函数可以让我们注册一个函数到 PHP 中,然后当我们使用一个类时,如果 PHP 找不到这个类,就会调用我们注册的函数来加载相应的类文件。

1
使用一个类 => 找不到?=> 调用注册的函数 => 加载类文件 => 找到了 => 类可以使用了

示例、注册一个类加载函数:

1
2
3
4
5
6
// 定义加载函数
function load($class)
{
}
// 注册 load 函数
spl_autoload_register('load');

代码说明:

  • 定义了一个 load 函数
  • 注册到 PHP 中
  • 当我们使用一个不存在的类时,load 函数会被调用,参数就是类的名字(包含命名空间)

示例、创建一个 testLoad 文件

testLoad.php

1
2
3
4
5
6
7
8
9
<?php
function load($class)
{
echo '我们需要加载:' . $class;
}
spl_autoload_register('load');

// 实例化
$blog = new app\models\Blog;

启动 PHP 服务器 php -S localhost:9999 然后浏览器中运行代码:

因为我们没有加载类文件,所以报错显示找不到类类,但同时也可以看到我们的 load 函数被执行了。

总结:当一个不存在的类被使用时,我们注册的函数会被自动调用。

魔术方法

在 OOP 中有一套特殊的方法,叫做魔术方法,它们的特点是:

  • 方法名以两个下划线开头(__)
  • 在某一时刻自动被调用

接下来我们来学习其中最重要的一个: __construct
其他的魔术方法在文末:

构造方法

__construct 是一个魔术方法,它在实例化一个类对象时会被调用,经常用来初始化类的数据,我们一般叫做:“构造方法”

自动调用

演示1、构造方法在实例化对象时被调用

每当使用 new 实例化一个类对象时,类中的构造方法就会被调用一次:

Boy.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Boy
{
public $name;

public function __construct()
{
echo 'hello world ';
}
}


$boy = new Boy; // hello world;
$boy1 = new Boy; // hello world;
初始数据

构造方法经常用来初始化类中的数据。我们通过在构造方法中添加参数来接收初始的数据。

Boy.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class Boy
{
public $name;

public function __construct($name)
{
$this->name = $name;
}

public function hello()
{
echo 'hello '.$name;
}
}

当一个类的构造方法上有参数时,在实例化这个类的对象时,必须要使用小括号依次设置参数值:

1
2
$boy = new Boy('小明');
$boy1 = new Boy('三毛');

示例、统计一个类所拥有的对象的总数。

要统计一个类被实例化的总数,原理很简单:每次实例化一个对象时就把计数加1。我们可以把代码写在构造方法中,因为每次使用 new 创建对象时,构造函数都会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class Boy
{
// 保存总的对象数
public static $count=0;

public function __construct()
{
// 每次加1
self::$count++;
}
}

$boy = new Boy;
$boy1 = new Boy;

// 打印总的记录数
echo Boy::$count;

静态成员

在类中我们可以使用 static 定义静态成员。可以是静态属性也可以是静态的方法:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Boy
{
public static $count=0;

public static function getCount()
{
return self::$count;
}
}

echo Boy::$count;

代码说明:

  • 在方法或者属性前添加 static 来定义静态成员
  • 在类外部使用 类名:: 来访问静态成员,如 Boy::$count , Boy::getCount()
  • 在类内部使用 self:: 访问静态成员,如:self::$count

静态成员和普通成员区别?

静态成员:静态成员属于类,无论有多少个对象,值只有一个。

普通成员:普通成员属于具体的对象,每个对象拥有自己的属性值。

比如:学生姓名就应该是一个普通属性,属于每一个同学,因为每个同学有自己的名字。而学生总人数这个属性就应该是一个静态属性,它属于“学生”这个大类,并不是某一个具体学生的属性。

同理,像身高、体重、性别这些都应该是普通属性,而平均年龄,最大年龄等应该属于静态属性。

继承

继承是面向对象三大特性之一,通过继承我们可以避免编写重复的代码,让我们的代码管理起来更加有组织有层次。

继承:一个类可以继承自另一个类,继承之后就拥有了那个类中所有非私有的属性和方法。

extends

PHP 中使用 extends 实现继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class People
{
public $name;

public function eat()
{
echo 'eat';
}
}

class Boy extends People
{

}

$boy = new Boy;
$boy->name = '小明';
$boy->eat(); // eat

代码说明

  • 一个类只能继承自一个父类(单继承),不能同时继承多个类
  • Boy 继承自 People ,所以 Boy 的类中就拥有了 name 属性和 eat 方法

避免重复代码

实际应用中,我们经常把多个类共有的方法制作成一个父类,然后让这个类继承自这个父类,这样相同的代码就只需要写一次:

动态绑定

继承时有一个特性:“动态绑定”,在实际应用中经常会用到,接下来我们来学习一下到底什么是动态绑定。

首先我们先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

class People
{
public $name='tom';
public function getName()
{
echo $this->name;
}
}

class Boy extends People
{
public $name='jack';
}

$boy = new Boy;
$people = new People;

$boy->getName();
$people->getName();

请问:最后两行分别输出的是什么?

$boy->getName() 输出的是 “jack”。

$people->getName() 输出的是 “tom”。

它们执行的代码是一样的,都是 echo $this->name ,为什么输出的结果却不同呢?

要知道原因,就要先搞清楚 $this 到底代表什么?

$this 代表实例化的那个对象。

new People 时,$this 就代表 People 类的对象,所以得到的是 People 类中的 name。

new Boy 时, $this 代表 Boy 类的对象,所以得到的就是 Boy 类中的 name。

总结:$this 的值是动态的,所以叫做动态绑定,即:$this->name 的值到底是什么我们在编写类时并不确定,只有在使用 new 实例化对象时才能确定。

练习:以下代码的输出结果是?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
class A
{
$name = 'tom';
public function getName()
{
echo $this->name;
}
}
class B extends A{

}
class C extends A{
$name = 'jack';
}
class D extends C{
$name = 'ali';
}

$b = new B;
$b->getName();

$c = new C;
$c->getName();

$d = new D;
$c->getName();

Trait

Trait:特质,可以让我们不使用继承就可以在多个类中复用方法的机制。

定义特质

使用 trait 来定义一个特质,特质中只能定义方法:

1
2
3
4
5
trait Fly
{
public function fly(){}
...
}

使用特质

定义了特质之后,我们就可以在一个类中使用 use 来引入这个特质,引入了特质之后,这个类就拥有了这个特质中的方法。

1
2
3
4
5
6
7
class Superman
{
use Fly;
}

$s = new Superman;
$s->fly();

总结:

  1. trait 可以用来向一个类中添加方法
  2. 不用继承就可以实现方法的复用

魔术方法:

  1. 构造函数:__construct():

    构造函数是类中的一个特殊函数,当我们使用new关键字实例化对象时,相当于调用了类的构造函数。

  2. 析构函数:__destruct():

    ①析构函数在对象被销毁释放之前自动调用;
    ②析构函数不能带有任何的参数;
    ③析构函数常用于对象使用完以后,释放资源,关闭资源等。

  3. __set($key,$value):

    给类私有属性赋值时自动调用,调用是给方法传递两个参数:需要设置的属性名、属性值

  4. __get($key):
    给获取类私有属性时自动调用,调用是给方法传递一个参数:需要获取的属性名
  5. isset($key):
    外部使用isset()函数检测私有属性时,自动调用。
    类外部使用isset();检测私有属性,默认检测不到(false)
    所以,我们可以使用
    isset()函数,在自动调用时,返回内部检测结果
  6. __unset($key):
    外部使用unset()函数删除私有属性时,自动调用;
  7. __clone:

    ① 当使用clone关键字,克隆对象时,自动调用clone函数
    ② __clone()函数类似于克隆是使用的构造函数,可以给新克隆对象赋初值
    ③ 克隆函数里面的$this指的是新克隆的对象

  8. __tostring()

    当使用echo等输出语句,直接打印对象时,调用 echo $zhangsan;那么, 可以指定tostring()方法的返回值,返回值需要是字符串。
    则使用echo函数打印时,将会打印出
    tostring()函数返回的字符串

  9. __call()

    调用类中未定义或未公开的方法时,会自动执行__call()方法,自动执行时,会给call方法传递两个参数:
    ① 调用的方法名
    ② (数组)调用的方法的参数列表

  10. __autoload()

    ① 这是唯一一个不在类中使用的魔术方法
    ② 当实例化一个不存在的类时,自动调用这个魔术方法
    ③ 调用时,会自动给autoload()传递一个参数:实例化的类名
    所以,可以实现 使用这个方法,自动加载类文件的功能:
    $zhangdan=new Person(“战三”);
    //本文件没有Person类,会自动执行
    autoload加载person.class.php文件
    复制代码

  11. sleep():
    ① 当执行对象串行化(将对象通过一系列操作,转化为字符串的过程,称为串行化)的时候,会自动执行
    sleep()函数;
    ② __sleep()函数要求返回一个数组,数组中的值,就是可以串行化的属性, 不在数组中的属性,不能被串行化。
  12. wakeup()
    ① 当反串行化对象时,自动调用
    wakeup()方法;
    ②自动调用时,用于给反串行化产生的新对象的属性,进行重新赋值;

 评论



本站使用 Material X 作为主题 , 总访问量为 次 。
隐藏