面向对象分上下篇,这里上篇涉及到的内容有:一、面向对象与面向过程有什么区别?  二、面向对象有什么特征?   三、什么是构造函数和析构函数?   四、面向对象的作用域范围有哪几种?   五、PHP 中魔术方法有哪些?

一、面向对象与面向过程有什么区别?
面向对象是当今软件开发方法的主流方法之一,它是把数据及对数据的操作方法放在一起,作为一个相互依存的整体,即对象。对同类对象抽象出其共性,即类,类中的大多数数据,只能被本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。例如,站在抽象的角度,人类具有身高、体重、年龄、血型等一些特称,人类会劳动、会直立行走、会吃饭、会用自己的头脑去创造工具等这些方法,人类仅仅只是一个抽象的概念,它是不存在的实体,但是所有具备人类这个群体的属性与方法的对象都称为人,这个对象人是实际存在的实体,每个人都是人这个群体的一个对象。

而面向过程是一种以事件为中心的开发方法,就是自顶向下顺序执行,逐步求精,其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构,各模块之间的关系也比较简单,在功能上相对独立,每一模块内部一般都是由顺序、选择和循环三种基本结构组成,其模块化实现的具体方法是使用子程序,而程序流程在写程序时就已经决定。例如五子棋,面向过程的设计思路就是首先分析问题的步骤:第一步,开始游戏;第二步,黑子先走;第三步,绘制画面;第四步,判断输赢;第五步,轮到白子;第六步,绘制画面;第七步,判断输赢;第八步,返回步骤二;第九步,输出最后结果。把上面每个步骤用分别的函数来实现,就是一个面向过程的开发方法。

具体而言,二者主要有以下几个方面的不同之处。
1)出发点不同。面向对象是用符合常规思维方式来处理客观世界的问题,强调把问题域的要领直接映射到对象及对象之间的接口上。而面向过程方法则不然,它强调的是过程的抽象化与模块化,它是以过程为中心构造或处理客观世界问题的。

2)层次逻辑关系不同。面向对象方法则是用计算机逻辑来模拟客观世界中的物理存在,以对象的集合类作为处理问题的基本单位,尽可能地使计算机世界向客观世界靠拢,以使问题的处理更清晰直接,面向对象方法是用类的层次结构来体现类之间的继承和发展。面向过程方法处理问题的基本单位是能清晰准确地表达过程的模块,用模块的层次结构概括模块或模块间的关系与功能,把客观世界的问题抽象成计算机可以处理的过程。

3)数据处理方式与控制程序方式不同。面向对象方法将数据与对应的代码封装成一个整体,原则上其他对象不能直接修改其数据,即对象的修改只能由自身的成员函数完成,控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是直接通过程序来处理数据,处理完毕后即可显示处理结果,在控制程序方式上是按照设计调用或返回程序,不能自由导航,各模块之间存在着控制与被控制、调用与被调用。

4)分析设计与编码转换方式不同。面向对象方法贯穿软件生命周期的分析、设计及编码之间是一种平滑过程,从分析到设计再到编码是采用一致性的模型表示,即实现的是一种无缝连接。而面向过程方法强调分析、设计及编码之间按规则进行转换,贯穿软件生命周期的分析、设计及编码之间,实现的是一种有缝的连接。
 
二、面向对象有什么特征?
面向对象的主要特征有抽象、继承、封装和多态。
1)抽象。抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。

2)继承。继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且子类可以修改或增加新的方法使之更适合特殊的需要。

3)封装。封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的信息进行隐藏。

4)多态。多态是指允许不同类的对象对同一消息做出响应。多态包括参数化多态和包含多态。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序函数同名问题。

三、什么是构造函数和析构函数?
1.构造函数
在PHP5之前的版本,构造函数的名字必须与类的名字相同,而从PHP5开始,开发者可以定义一个名为__construct的方法作为构造函数。构造函数的作用就是当类被实例化的时候会被自动调用,因此构造函数主要用于做一些初始化的工作。使用__construct作为构造函数名字的一个好处是,当类名修改的时候,不需要修改构造函数的名字。它的声明形式为
void __construct ([ mixed $args [, $... ]] )

在C++语言中,子类的构造函数会隐式地调用父类的无参数的构造函数。但是在PHP中,子类的构造函数不会隐式地去调用父类的构造函数,需要开发者通过parent::__construct()来显式地去调用父类的构造函数。当子类没有定义构造函数的时候,它会继承父类的构造函数,但前提是父类的构造函数不能被定义为private。使用示例如下:

<?php

class BaseClass {

    function __construct() {

        print "Base constructor

";

    }

}

class SubClass extends BaseClass {

    function __construct() {

        parent::__construct();

        print "Sub constructor

";

    }

}

// 会调用父类构造函数

$obj = new BaseClass();

//调用子类构造函数,子类构造函数会去调用父类构造函数

$obj = new SubClass();

?>

程序的运行结果为
Base constructor 
Base constructor
Sub constructor

从上面的讲解中可以发现,从PHP5开始多了一种构造函数定义的方法。为了实现不同版本PHP代码的兼容,在PHP5的类中找不到 __construct() 函数并且也没有从父类继承一个的话,那么它就会尝试寻找旧式的构造函数(与类同名的函数)。这种兼容的方法存在一个风险:在PHP5之前的版本中开发的类中已有一个名为 __construct() 的方法却被用于其他用途时,PHP5的类会认为这是一个构造函数,从而当类实例化时自动执行这个方法。

从 PHP 5.3.3 开始,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。

2.析构函数
析构函数是在PHP5引入的,它的作用与调用时机和构造函数刚好相反,它在对象被销毁时自动执行。析构函数__destruct()结构形式如下:

function __destruct(){

/* 类的初始化代码*/

}
需要注意的是,析构函数是由系统自动调用的,因此,它不需要参数。
默认情况下,系统仅释放对象属性所占用的内存,并不销毁在对象内部申请的资源(例如,打开文件、创建数据库的连接等),而利用析构函数在使用一个对象之后执行代码来清除这些在对象内部申请的资源(关闭文件、断开与数据库的连接)。

与构造函数类似,如果想在子类中调用父类的析构函数,那么需要显式地调用:parent::__destruct()。如果子类没有定义析构函数,那么它会继承父类的析构函数。

当对象不再被引用时,将调用析构函数。如果要明确地销毁一个对象,那么可以给指向对象的变量不分配任何值,通常将变量赋值为NULL或者用unset()函数。示例代码如下:

<?php

class des{
    function __destruct(){
        echo "对象被销毁,执行析构函数&lt;br&gt;";
    }

}

$p=new des(); /* 实例化类 */
echo "程序开始&lt;br&gt;";
unset($p); /* 销毁变量$p */
echo "程序结束";

?>

四、面向对象的作用域范围有哪几种?
在PHP5中,类的属性或者方法主要有public、protected和private三种类作用域,它们的区别如下:
1)public(公有类型)表示全局,类内部、外部和子类都可以访问。
默认的访问权限为public,也就是说,如果一个方法没有被public、protected或private修饰,那么它默认的作用域为public。

2)protected(受保护类型)表示受保护的,只有本类或子类可以访问。
在子类中,可以通过self::var或self::method访问,也可以通过parent::method来调用父类中的方法。
在类的实例化对象中,不能通过$obj->var来访问protected类型的方法或属性。

3)private(私有类型)表示私有的,只有本类内部可以使用。
该类型的属性或方法只能在该类中使用,在该类的实例、子类、子类的实例中都不能调用私有类型的属性和方法。
 
五、PHP种魔术方法有哪些?
在PHP中,把所有以__(两个下画线)开头的类方法保留为魔术方法。所以在定义类方法时,不建议使用 __ 作为方法的前缀。下面分别介绍每个魔术方法的作用。
1.__get、__set、__isset、__unset
这四个方法是为在类和它们的父类中没有声明的属性而设计的。
1)在访问类属性的时候,若属性可以访问,则直接返回;若不可以被访问,则调用__get 函数。
方法签名为:public mixed __get ( string $name )
2)在设置一个对象的属性时,若属性可以访问,则直接赋值;若不可以被访问,则调用__set 函数。
方法签名为:public void __set ( string $name , mixed $value )
3)当对不可访问的属性调用 isset() 或 empty() 时,__isset() 会被调用。
方法签名为:public bool __isset ( string $name )
4)当对不可访问属性调用 unset() 时,__unset() 会被调用。
方法签名为:public bool _unset ( string $name )

需要注意的是,以上存在的不可访问包括属性没有定义,或者属性的访问控制为proteced或private(没有访问权限的属性)。
下面通过一个例子把对象变量保存在另外一个数组中。

<?php

class Test
{

 /* 保存未定义的对象变量 */
 private $data = array();
 public function __set($name, $value){
    $this-&gt;data[$name] = $value;

 }

 public function __get($name){
    if(array_key_exists($name, $this-&gt;data))
        return $this-&gt;data[$name];
    return NULL;

 }

 public function __isset($name){
    return isset($this-&gt;data[$name]);
 }

 public function __unset($name){
    unset($this-&gt;data[$name]);
 }

}

$obj = new Test;
$obj->a = 1;
echo $obj->a . "
";

?>

程序的运行结果为
1

2.__construct、__destruct
1)__construct 构造函数,实例化对象时被调用。
2)__destruct 析构函数,当对象被销毁时调用。通常情况下,PHP只会释放对象所占有的内存和相关的资源,对于程序员自己申请的资源,需要显式地去释放。通常可以把需要释放资源的操作放在析构方法中,这样可以保证在对象被释放的时候,程序员自己申请的资源也能被释放。

例如,可以在构造函数中打开一个文件,然后在析构函数中关闭文件。

<?php

class Test
{

 protected $file = NULL;

 function __construct(){
    $this-&gt;file = fopen("test","r");

 }

 function __destruct(){
    fclose($this-&gt;file);

 }

}

?>

3.__call()和__callStatic()
1)__call( $method, $arg_array ):当调用一个不可访问的方法时会调用这个方法。
2)__callStatic的工作方式与 __call() 类似,当调用的静态方法不存在或权限不足时,会自动调用__callStatic()。

使用示例如下:

<?php

class Test
{

 public function __call ($name, $arguments) {
   echo "调用对象方法 '$name' ". implode(', ', $arguments). "

";

}

 public static function __callStatic ($name, $arguments) {
    echo "调用静态方法 '$name' ". implode(', ', $arguments). "

";

 }

}

$obj = new Test;
$obj->method1('参数1');
Test::method2('参数2');

?>

程序的运行结果为
调用对象方法 'method1' 参数1 
调用静态方法 'method2' 参数2

4.__sleep()和__wakeup()
1)__sleep 串行化的时候调用。
2)__wakeup 反串行化的时候调用。
也就是说,在执行serialize()和unserialize()时,会先调用这两个函数。例如,在序列化一个对象时,如果这个对象有一个数据库连接,想要在反序列化中恢复这个连接的状态,那么就可以通过重载这两个方法来实现。示例代码如下:

<?php

class Test
{

 public $conn;
 private $server, $user, $pwd, $db;
 public function __construct($server, $user, $pwd, $db)
 {
     $this-&gt;server = $server;
     $this-&gt;user = $user;
     $this-&gt;pwd = $pwd;
     $this-&gt;db = $db;
     $this-&gt;connect();

 }

 private function connect()
 {
    $this-&gt;conn = mysql_connect($this-&gt;server, $this-&gt;user, $this-&gt;pwd);
    mysql_select_db($this-&gt;db, $this-&gt;conn);

 }

 public function __sleep()
 {
    return array('server', 'user', 'pwd', 'db');

 }

 public function __wakeup()
 {
    $this-&gt;connect();
 }

 public function __destruct(){
    mysql_close($conn);

 }

}

?>

5.__toString()
__toString 在打印一个对象时被调用,可以在这个方法中实现想要打印的对象的信息,使用示例如下:

<?php

class Test
{

 public $age;
 public function __toString() {
    return "age:$this-&gt;age";

 }

}

$obj = new Test();
$obj->age=20;
echo $obj;

?>

程序的运行结果为
age:20

6.__invoke()
在引入这个魔术方法后,可以把对象名当作方法直接调用,它会间接调用这个方法,使用示例如下:

<?php

class Test
{

 public function __invoke()
 {
    print "hello world";

 }

}

$obj = new Test;
$obj();

?>
程序的运行结果为

hello world

7.__set_state()
调用 var_export 时被调用,用__set_state的返回值作为var_export 的返回值。使用示例如下:

<?php

class People
{

 public $name;
 public $age;
 public static function __set_state ($arr) {

    $obj = new People;
    $obj-&gt;name = $arr['name'];
    $obj-&gt;age = $arr['aage'];
    return $obj;

 }

}

$p = new People;
$p->age = 20;
$p->name = 'James';
var_dump(var_export($p));

?>

程序的运行结果为

People::__set_state(array(

'name' => 'James',
'age' => 20,

)) NULL

8.__clone()
这个方法在对象克隆的时候被调用,php提供的__clone()方法对一个对象实例进行浅拷贝,也就是说,对对象内的基本数值类型通过值传递完成拷贝,当对象内部有对象成员变量的时候,最好重写__clone方法来实现对这个对象变量的深拷贝。使用示例如下:

<?php

class People
{

 public $age;
 public function __toString() {
    return "age:$this-&gt;age 

";

 }

}

class MyCloneable
{

 public $people;
 function __clone()
 {
    $this-&gt;people = clone $this-&gt;people; //实现对象的深拷贝

 }

}

$obj1 = new MyCloneable();
$obj1->people = new People();
$obj1->people->age=20;
$obj2 = clone $obj1;
$obj2->people->age=30;
echo $obj1->people;
echo $obj2->people;

?>
程序的运行结果为

age:20 age:30
由此可见,通过对象拷贝后,对其中一个对象值的修改不影响另外一个对象。

9.__autoload()
当实例化一个对象时,如果对应的类不存在,则该方法被调用。这个方法经常的使用方法为:在方法体中根据类名,找出类文件,然后require_one 导入这个文件。由此,就可以成功地创建对象了,使用示例如下:
Test.php:

<?php

class Test {

function hello() {
    echo 'Hello world';

}

}

?>

index.php:

<?php

function __autoload( $class ) {

$file = $class . '.php';  
if ( is_file($file) ) {  
    require_once($file);   //导入文件

}

}

$obj = new Test();
$obj->hello();

?>
程序的运行结果为

Hello world
在index.php中,由于没有包含Test.php,在实例化Test对象的时候会自动调用__autoload方法,参数$class的值即为类名Test,这个函数中会把Test.php引进来,由此Test对象可以被正确地实例化。
这种方法的缺点是需要在代码中文件路径做硬编码,当修改文件结构的时候,代码也要跟着修改。另一方面,当多个项目之间需要相互引用代码的时候,每个项目中可能都有自己的__autoload,这样会导致两个__autoload冲突。当然可以把__autoload修改成一个。这会导致代码的可扩展性和可维护性降低。由此从PHP5.1开始引入了spl_autoload,可以通过spl_autoload_register注册多个自定义的autoload方法,使用示例如下:

index.php

<?php

function loadprint( $class ) {

$file = $class . '.php';  
if (is_file($file)) {  
    require_once($file);  

} 

}

spl_autoload_register( 'loadprint' ); //注册自定义的autoload方法从而避免冲突
$obj = new Test();
$obj->hello();

?>
spl_autoload是_autoload()的默认实现,它会去include_path中寻找$class_name(.php/.inc) 。除了常用的spl_autoload_register外,还有如下几个方法:
1)spl_autoload:_autoload()的默认实现。
2)spl_autoload_call:这个方法会尝试调用所有已经注册的__autoload方法来加载请求的类。
3)spl_autoload_functions:获取所有被注册的__autoload方法。
4)spl_autoload_register:注册__autoload方法。
5)spl_autoload_unregister:注销已经注册的__autoload方法。
6)spl_autoload_extensions:注册并且返回spl_autoload方法使用的默认文件的扩展名。

引申:PHP有哪些魔术常量?
除了魔术变量外,PHP还定义了如下几个常用的魔术常量。
1)__LINE__:返回文件中当前的行号。
2)__FILE__:返回当前文件的完整路径。
3)__FUNCTION__:返回所在函数名字。
4)__CLASS__:返回所在类的名字。
5)__METHOD__:返回所在类方法的名称。与__FUNCTION__不同的是,__METHOD__返回的是“class::function”的形式,而__FUNCTION__返回“function”的形式。
6)__DIR__:返回文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录(PHP 5.3.0中新增)。
7)__NAMESPACE__:返回当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。
8)__TRAIT__:返回 Trait 被定义时的名字。Trait 名包括其被声明的作用区域(PHP 5.4.0 新增)。

Last modification:February 2, 2021
如果觉得我的文章对你有用,请随意赞赏