首頁 >後端開發 >php教程 >PHP物件導向特性

PHP物件導向特性

little bottle
little bottle轉載
2019-04-16 09:53:003368瀏覽

物件導向的三個主要特性是封裝、繼承和多型。

建立物件

$对象名 = new 类名();
$对象名 = new 类名;
  • new 是一個關鍵字,表示建立一個新的實例。
  • 在類別定義內部,可以用 new selfnew parent 建立新物件。

成員屬性

成員屬性必須要有存取修飾符,如果不寫,會報錯。

成員屬性的預設值為NULL。

範例:

class Test {
    
    public $name; //共有
    protected $height; //保护
    private $age; //私有
}

成員方法

成員方法如果不寫存取修飾符,則預設為public。

我們可以指定成員方法裡的參數的類型,如類別類型,或陣列類型(array),也可以是介面類型,但是介面類型要注意,因為介面沒有實例,當我們宣告參數類型為介面類型的時候,那麼傳入的參數只要是實作了該介面的類別的實例即可。

範例:

class Test {
    
    function hello() {} //默认为public
    public function say() {} //公有
    protected function smile() {} //保护
    private function laugh() {} //私有
}

建構方法

  • 一個類別裡只能有一個建構方法
  • 沒有傳回值(即使在建構方法裡寫了回傳值也沒有意義)
  • 在建立一個類別的新物件時,系統會自動的呼叫類別的建構方法完成對新物件的初始化。
  • 如果我們沒有重寫預設建構函數,那麼系統會預設幫我們建立一個沒有參數的建構函數,public function __construct(){},如果我們重寫了建構函數,那麼我們自訂的建構函式就會覆蓋系統預設的建構函式。
  • 如果我們自訂的建構函式裡有行參,那麼當我們建立物件的時候就必須傳入對應的實參。否則會報錯。

範例:

class Test {

    public function __construct() {
        echo &#39;hello, world<br>&#39;;
    }
}

$testObj = new Test();


class One {

    public function __construct($param) {

        echo $param;
    }
}

$oneObj = new One(&#39;hello, world<br>&#39;);

輸出結果:

hello, world
hello, world

析構方法

  • 當一個物件沒有被任何變數引用時就會自動銷毀,並在被銷毀前自動呼叫析構函數,無論程式有沒有結束,只要一個物件沒有被任何變數引用就會被銷毀。
  • 當PHP程式運行結束後,所有的物件都會被銷毀,物件在被銷毀前,會自動呼叫析構函數。
  • 預設情況下,析構函數銷毀物件的順序和建立物件的順序正好相反。最先創建的對象最後被銷毀,強調默認情況下是因為,我們可以手動銷毀對象,這樣的話對象銷毀的順序就和默認情況不一樣了,當PHP程序執行完後,那時候銷毀對象的方法就是採用的預設方法。
  • 如果我們定義的一個類別裡專門有個屬性是用來連接資料庫的,當我們創建了該類別的實例對象,並且用該成員屬性連接資料庫後,如果該類型的實例對象佔用資源較大,我們往往會在使用後就立即銷毀,雖然該物件能被銷毀,但是該物件的成員屬性所連接的資源卻不會被自動銷毀,需要我們自己手動銷毀,這時,我們可以在析構函數裡加入一句關閉資料庫連線的語句就好了(mysql_close(要關閉的資源))。

範例:

class Test {

    public function __construct() {
        echo &#39;hello, world<br>&#39;;
    }

    public function __destruct()
    {
        echo &#39;执行一些回收资源的操作<br>&#39;;
    }
}

$testObj = new Test();

輸出結果:

hello, world
//执行一些回收资源的操作

垃圾回收機制

  • 在PHP中,當一個物件沒有任何引用指向它的時候,就會成為一個垃圾對象,PHP將啟用垃圾回收器將對象銷毀。
  • 在PHP程式退出前,PHP也會啟用垃圾回收器,銷毀物件。

存取修飾符

  • public

    如果沒有為方法指定存取修飾符,它將是public# 。公有的屬性或方法可以在類別的內部或外部進行存取。

  • protected

    如果一個屬性或方法指定存取修飾符為protected,那麼被標記的屬性或方法只能在類別內部訪問,被修飾的屬性和方法能被子類別繼承。

  • private

    如果一個屬性或方法指定存取修飾符為private,那麼被標記的屬性或方法只能在類別內部訪問,私有的屬性和方法將不會被繼承。

魔術方法

  1. 魔術方法都是在滿足某個條件時,由系統自動呼叫。

  2. 魔術方法的名字,都是以兩個底線(__)開頭的,因此我們在自訂函數時,函數名稱不要以兩個底線開頭。

  3. 一些常用的魔術方法

    __construct():
     构造方法
    __destruct():
     析构方法
    __set():
     在给不可访问属性赋值(比如:protected/private/不存在)时,__set()会被调用。
    __get():
     读取不可访问属性的值(比如:protected/private/不存在)时,__get()会被调用。
    __isset():
     当对不可访问属性(比如:protected/private/不存在)调用isset()或empty()时,__isset()会被调用。
    __unset():
     当对不可访问属性(比如:protected/private/不存在)调用unset()时,__unset()会被调用。
    __toString():
     当我们把一个对象当做字符串输出时,就会自动调用__stoString()方法。
    __clone():
     如果定义了__clone方法,则新创建的对象clone(复制生成的对象)中的__clone()会被调用。用法如下:
     $copy_of_object = clone $object;
     克隆后的对象与原对象的地址不一样。
     当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。
     如果想防止其他程序员克隆某个对象,可以重写__clone()方法并把访问修饰符设置为private。
    __call():
     在对象中调用一个不可访问(比如函数访问修饰符为protected/private/不存在)方法时,__call()会被调用。
    __callStatic():
     用静态方式中调用一个不可访问方法时,__callStatic() 会被调用。

物件比較

#當使用比較運算子(==)比較兩個物件變數時,比較的原則是:如果兩個物件的屬性和屬性值都相等,而兩個物件是同一個類別的實例,那麼這兩個物件變數相等。

而如果使用全等運算子(===),這兩個物件變數一定要指向某個類別的同一個實例(即同一個物件)。

範例:

class Test {

    public $name = &#39;itbsl&#39;;
    public $age  = 25;
}

$testObj  = new Test();
$testObj2 = new Test();

if ($testObj == $testObj2) {
    echo &#39;相等<br>&#39;;
} else {
    echo &#39;不相等<br>&#39;;
}

$testObj2->name = &#39;jack&#39;;

if ($testObj == $testObj2) {
    echo &#39;相等<br>&#39;;
} else {
    echo &#39;不相等<br>&#39;;
}

继承

即使父类的某些属性设置为私有的,子类仍然能够继承,但是对子类却是不可见的。当子类对象使用父类的私有属性的时候,会自动触发重载机制,在子类创建一个和父类私有属性同名的属性。此时子类使用的属性并不是父类的私有属性了,而是通过重载创建的和父类私有属性同名的属性罢了。

如果在子类中需要访问其父类的构造方法( 方法的访问修饰符是public/protected)可以使用父类::方法名(或者 parent::方法名)来完成。(推荐使用parent::方法名,并且一般调用父类构造方法都是parent::__construct();)

调用父类的普通方法直接用this->方法名即可。(也可以用父类名::方法名或者 parent::方法名 ,但是不推荐)

继承并不是直接把父类的属性和方法直接拷贝到子类里面来。而是建立了一种关联。原本我以为是直接把父类的属性和方法直接拷贝过来,最后发现只是建立了一种关联。下图证明了不是直接拷贝,当在子类里调用父类的方法时,父类输出自己的属性时输出结果都和初始化不一样了(private属性除外,因为是私有的,子类无法看见,所以子类也无法重写父类的私有属性),如果是直接拷贝的话,那么当调用父类的方法时应该输出100,200,300才对,所以说继承不是直接拷贝。

class A {

    public    $num1 = 100;
    protected $num2 = 200;
    private   $num3 = 300;

    public function show1() {
        echo &#39;num1 = &#39; . $this->num1 . &#39;<br>&#39;;
    }

    public function show2() {
        echo &#39;num2 = &#39; . $this->num2 . &#39;<br>&#39;;
    }

    public function show3() {
        echo &#39;num3 = &#39; . $this->num3 . &#39;<br>&#39;;
    }
}

class B extends A {

    public    $num1 = 1;
    protected $num2 = 2;
    private   $num3 = 3;

    public function show1() {
        echo &#39;num1 = &#39; . $this->num1 . &#39;<br>&#39;;
        parent::show1();
    }

    public function show2() {
        echo &#39;num2 = &#39; . $this->num2 . &#39;<br>&#39;;
        parent::show2();
    }

    public function show3() {
        echo &#39;num3 = &#39; . $this->num3 . &#39;<br>&#39;;
        parent::show3();
    }
}

$bObj = new B();
$bObj->show1();
$bObj->show2();
$bObj->show3();

输出结果:

num1 = 1
num1 = 1
num2 = 2
num2 = 2
num3 = 3
num3 = 300

重载

PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法。我们是通过魔术方法(magic methods)来实现的。

当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。本节后面将使用"不可访问属性(inaccessible properties)"和"不可访问方法(inaccessible methods)"来称呼这些未定义或不可见的类属性或方法。

属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被声明为 static。从 PHP5.3.0 起, 将这些魔术方法定义为 static 会产生一个警告。

所有重载方法都必须被声明为public。

这些魔术方法都参数都不能通过引用传递。

PHP中的"重载"与其它绝大多数面向对象语言不同。传统的"重载"是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

因为PHP处理赋值运算的方式,__set()的返回值将被忽略。类似的,在下面这样的链式赋值中,__get()不会被调用。

$a = $obj->b = 8;

在除 isset() 外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。

为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。

//在对象中调用一个不可访问方法时,__call() 会被调用。
public mixed __call(string $name , array $arguments)

//用静态方式中调用一个不可访问方法时,__callStatic() 会被调用。
public static mixed __callStatic(string $name , array $arguments)

属性重载

我们曾经提过,当我们访问或给一个不可访问(protected/private)的属性或者不存在的属性赋值时,就会调用相应的系统魔术方法__get($property)__set($property, $value),如果我们不重写这两个方法,当我们给不存在的属性赋值时,系统会自动帮我们创建一个public的属性,我们可以自己重写这两个方法来管理这些这些动态创建的属性。或者直接不让产生动态属性。当我们不重写这两个方法就不可访问属性赋值时,会报致命错误。

示例:

class Obj {

    protected $name = &#39;itbsl&#39;;



}

$obj = new Obj();
$obj->name = "jack"; //此处会报致命错误

var_dump($obj);

错误信息为:

Fatal error: Uncaught Error: Cannot access protected property Obj::$name

当我们重写了__set()方法就没问题了

class Obj {

    protected $str = &#39;itbsl&#39;;

    public function __set($name, $value)
    {
        $this->$name = $value;
    }

}

$obj = new Obj();
$obj->str = "jack";

var_dump($obj);

属性重载可以帮助我们动态管理新的属性也可以禁止动态创建属性。

(1)动态管理

class Dog {

    //定义一个数组,管理我们动态增加的属性和值
    private $arr = [];
    
    //这里我们重写__set来管理动态增加的属性
    public function __set($name, $value)
    {
        $this->arr[$name] = $value;
    }
    
    public function __get($name)
    {
        return isset($this->arr[$name]) ? $this->arr[$name] : null;
    }
}

(2)禁止创建动态属性。重写set方法,里面什么也不做。

class Dog {
    
    //这里我们重写__set来管理动态增加的属性
    public function __set($name, $value)
    {
       //just do nothing
    }  
}

方法重写

在子类中重写父类的方法时要,重写的方法的访问控制符不能比父类的方法的访问控制符的级别小。例如:如果父类的访问控制符为public,则子类重写方法的访问修饰符只能为public,如果父类的为protected,则子类的访问修饰控制符可以为protected或public。重写属性也要遵守上面的规则

属性重写

如果子类有和父类相同的属性,如果属性是public或者protected则会重写父类的属性,如果是private则创建一个同名的新私有属性,同时仍然会继承父类的同名私有属性。

静态属性

  1. 静态属性不属于某个对象,而是所有对象共享的属性,每个对象都可以访问它。
  2. 静态属性属于类的范畴,而不是某个对象的独有特性。
  3. 在类中,使用和访问静态变量的方式是 self::$静态属性
  4. 在类外,使用和访问静态变量的方式是 类名::$静态属性(要求访问修饰符为public)。
  5. 当我们用var_dump()输出一个对象的时候,该对象的静态变量不会被输出。
  6. 就像其它所有的 PHP 静态变量一样,静态属性只能被初始化为文字或常量,不能使用表达式。所以可以把静态属性初始化为整数或数组,但不能初始化为另一个变量或函数返回值,也不能指向一个对象。

静态方法

  1. 静态方法的访问方式为 类名::静态方法名(); 同时也可以用对象名->静态方法名();和对象名::静态方法名(),但是后两种不推荐,尽量只用第一种。
  2. 在类的外部调用静态方法,要求静态方法的访问修饰符必须是public的。
  3. 在类内部调用静态方法: self::静态方法 或者 类名::静态方法 通过$this也可以。只推荐第一种方式。在类的内部访问静态方法,无论是什么修饰符都可以访问静态方法。
  4. 静态方法中不可以访问非静态属性和非静态方法。
  5. 普通的成员方法,可以访问静态属性。

静态属性和普通属性的区别:

(1)加上static称静态变量,否则就是普通属性

(2)静态属性是与类相关的,所有对象共享的属性

(3)普通属性属于每个对象个体的属性。

多态

由于PHP变量没有类型限制,所以PHP是天生支持多态的。

类型约束

类型约束支持的类型有 array 、 callable 、 对象类型 、 接口

抽象类

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法[抽象方法],用abstract来修饰该类[抽象类]。

(1) 如果你希望把某个方法做成 抽象方法 ,则前面写上 abstract

(2) 如果一个类中有抽象方法,那么该类必须声明为抽象类。

(3) 抽象类最主要的作用在于设计,它的目的是让其它的类继承它,并实现其中的抽象方法。如果子类继承了该抽象类,除非继承该抽象类的子类也被声明为抽象类,否则必须实现抽象类中所有的抽象方法,如果不实现就会报错。

(4) 抽象类不能被实例化

(5) 抽象类可以没有abstract方法

(6) 抽象类可以有非抽象方法,成员属性和常量

(7) 抽象方法不能有函数体

基本语法:

abstract class 类名 {
    abstract 修饰符 function 函数名(参数列表);
}

普通类如何继承抽象类?

abstract class Superman {
    public $name;
    public $age;
    
    public function __construct($name, $age) {
        
        $this->name = $name;
        $this->age  = $age;
    }
    
    abstract public function run();
    abstract public function fly();
    abstract public function attach();
}

class Spiderman extends Superman {
    
    public function run()
    {
        echo &#39;Spiderman is running on the net.<br>&#39;;
    }
    
    public function fly()
    {
        echo &#39;Spiderman can hang in the sky through net.<br>&#39;;
    }
    
    public function attach()
    {
        echo &#39;Spider attach.<br>&#39;;
    }
}

接口

(1) 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。

(2)基本语法:

interface 接口名 {
    //方法[不含方法体]
}

(3)接口中所有的方法都不能有主体

(4)一个类可以实现多个接口,接口名之间用逗号隔开

class 类名 implements 接口1, 接口2 {
    
}

(5)接口中可以有属性,但只能是常量,并且是公开可访问的。默认是public,但不能用public显式修饰

(6)接口中的方法都必须是public的,默认就是public

(7)一个接口不能继承其它的类,但是可以继承别的接口,而且一个接口可以继承多个接口

interface 接口名 extends 接口1, 接口2 {
    //方法[不含方法体]
}

(8)接口不能被实例化

(9)接口是更加抽象的抽象类,接口里的所有方法都没有方法体。接口体现了程序设计的多态和高内聚低耦合的设计思想。

(10)说明:

接口的命名规范一般是字符 i 开头,然后第二个字符大写,形式如:iXxxx,比如:iUsb

(11)如何使用接口中的常量

接口名::常量名;

如果某个类要实现接口,需要用implements 关键字。并且实现接口里的所有方法,如果该类要实现多个接口,则所有的接口的所有的方法都要实现,只要存在没有实现的接口里的方法就会报错。

示例:

interface Displayable {
    function display();
}

class Base {
    function operation() {
        echo &#39;operate something.<br>&#39;;
    }
}

class SubClass extends Base implements Displayable {

    function display()
    {
        echo &#39;display.<br>&#39;;
    }
}

$temp = new SubClass();
$temp->display();

final关键字

(1) 作用:

因为安全的考虑,类的某个方法不允许子类通过重写来修改。

不希望某个类被其它的类继承。

(2) PHP5新增了一个final关键字。如果父类中的方法被声明为final,则子类无法覆盖该方法。如果一个类被声明为final,则该类不能被继承。

(3) final不能够修饰成员属性(变量)

(4) final方法不能被重写,但可以被继承。即使是被声明为final的方法也依然能够被继承并被使用,只是不能重写(修改)罢了。

(5) 一般来说,final类中不会出现final方法,因为final类都不能被继承,也就不会去重写override final类的方法了。

(6) final类是可以被实例化的

类常量

可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。

常量的值必须是一个定值,不能是变量、类属性、数学运算的结果或函数调用。

接口(interface)中也可以定义常量。更多示例见文档中的接口部分。

自 PHP 5.3.0 起,可以用一个变量来动态调用类。但该变量的值不能为关键字(如 selfparentstatic)。

细节说明:

(1) 常量名一般字母全部大写:TAX_RATE,中间可以有下划线

(2) 在定义常量的同时,必须赋初值,比如 const TAX_RATE = 1.1

(3) const关键字前不能用public/protected/private修饰。默认是public

(4) 访问常量

在类的外部 类名::常量名 接口名::常量名

在类的内部 类名::常量名 self::常量名

(5) 常量的值在定义的时候就初始化,以后就不能修改

(6) 常量可以被子类继承

(7) 一个常量是属于一个类的,而不是某个对象的。

(8) 常量是全局性的,可以在任何地方访问。

(9) 在类里面不能用define定义常量。

(10) 常量的值可以是基本类型数据和数组。不可以是对象。

对象遍历

foreach用法和之前的数组遍历是一样的,只不过这里遍历的key是属性名,value是属性值。在类外部遍历时,只能遍历到public属性的,因为其它的都是受保护的,类外部不可见。

示例:

class HardDiskDrive {

    public $brand;
    public $color;
    public $cpu;
    public $workState;

    protected $memory;
    protected $hardDisk;

    private $price;

    public function __construct($brand, $color, $cpu, $workState, $memory, $hardDisk, $price) {

        $this->brand = $brand;
        $this->color = $color;
        $this->cpu   = $cpu;
        $this->workState = $workState;
        $this->memory = $memory;
        $this->hardDisk = $hardDisk;
        $this->price = $price;
    }

}

$hardDiskDrive = new HardDiskDrive(&#39;希捷&#39;, &#39;silver&#39;, &#39;tencent&#39;, &#39;well&#39;, &#39;1T&#39;, &#39;hard&#39;, &#39;$456&#39;);

foreach ($hardDiskDrive as $property => $value) {

    var_dump($property, $value);
    echo &#39;<br>&#39;;
}

输出结果为:

string(5) "brand" string(6) "希捷" 
string(5) "color" string(6) "silver" 
string(3) "cpu" string(7) "tencent" 
string(9) "workState" string(4) "well"

如果我们想遍历出对象的所有属性,就需要控制foreach的行为,就需要给类对象,提供更多的功能,需要继承自Iterator的接口:

该接口,实现了foreach需要的每个操作。foreach的执行流程如下图:

PHP物件導向特性

看图例中,foreach中有几个关键步骤:5个。

而Iterator迭代器中所要求的实现的5个方法,就是用来帮助foreach,实现在遍历对象时的5个关键步骤:

当foreach去遍历对象时, 如果发现对象实现了Ierator接口, 则执行以上5个步骤时, 不是foreach的默认行为, 而是调用对象的对应方法即可:

PHP物件導向特性

示例代码:

class Team implements Iterator {

    //private $name = &#39;itbsl&#39;;
    //private $age  = 25;
    //private $hobby = &#39;fishing&#39;;

    private $info = [&#39;itbsl&#39;, 25, &#39;fishing&#39;];

    public function rewind()
    {
        reset($this->info); //重置数组指针
    }

    public function valid()
    {
        //如果为null,表示没有元素,返回false
        //如果不为null,返回true

        return !is_null(key($this->info));
    }

    public function current()
    {
        return current($this->info);
    }

    public function key()
    {
        return key($this->info);
    }

    public function next()
    {
        return next($this->info);
    }

}

$team = new Team();

foreach ($team as $property => $value) {

    var_dump($property, $value);
    echo &#39;<br>&#39;;
}

PHP内置标准类

如果我们希望把一些数据,以对象的属性的方式存储,同时我们又不想定义一个类,可以考虑使用PHP内置标准类stdClass[standard标准]

基本用法:

//注意: stdClass()不是我们自定义的类,而是PHP内置的
$person = new stdClass();

然后我们就可以直接用$person->属性的方式来使用,属性都不是系统的,我们自己也没定义,但是当我们使用这个属性的时候,系统发现没有定义就会调用重载方法,重载这个属性。自动帮我们创建这个属性。

其它数据类型转成对象

如果,我们希望把非对象类型转换成对象,可以通过如下方法实现,(object)来强制转换,把这些数据转换成标准内置类stdClass,此种转换不改变原有数据类型

【推荐课程:PHP视频教程

以上是PHP物件導向特性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除