PHP基礎之物件導向講解

上善若泪發表於2024-08-25

目錄
  • 1 物件導向
    • 1.1 PHP 類定義
    • 1.2 建立物件
    • 1.3 構造&解構函式
      • 1.3.1 建構函式
      • 1.3.2 解構函式
    • 1.4 繼承
      • 1.4.1 方法重寫
      • 1.4.2 父子類初始化順序
      • 1.4.3 示例說明
    • 1.5 訪問控制
      • 1.5.1 屬性的訪問控制
      • 1.5.2 方法的訪問控制
    • 1.6 介面 & 抽象
      • 1.6.1 介面
      • 1.6.2 抽象類
    • 1.7 關鍵字
      • 1.7.1 parent,self,this
        • 1.7.1.1 parent
        • 1.7.1.2 self
        • 1.7.1.3 this
      • 1.7.2 常量
      • 1.7.3 Static
      • 1.7.4 Final

1 物件導向

1.1 PHP 類定義

PHP 定義類通常語法格式如下:

<?php
class Site {
  /* 成員變數 */
  var $url;
  var $title;
  
  /* 成員函式 */
  function setUrl($par){
     $this->url = $par;
  }
  
  function getUrl(){
     echo $this->url . PHP_EOL;
  }
  
  function setTitle($par){
     $this->title = $par;
  }
  
  function getTitle(){
     echo $this->title . PHP_EOL;
  }
}
?>

解析如下:

  • 類使用 class 關鍵字後加上類名定義。
  • 類名後的一對大括號 {} 內可以定義變數和方法。
  • 類的變數使用 var 來宣告, 變數也可以初始化值。
  • 函式定義類似 PHP 函式的定義,但函式只能透過該類及其例項化的物件訪問。
  • 變數 $this 代表自身的物件。
  • PHP_EOL 為換行符。

1.2 建立物件

類建立後,我們可以使用 new 運算子來例項化該類的物件:

$baidu = new Site;
$taobao = new Site;
$google = new Site;

以上程式碼我們建立了三個物件,三個物件各自都是獨立的,接下來我們來看看如何訪問成員方法與成員變數。

在例項化物件後,我們可以使用該物件呼叫成員方法,該物件的成員方法只能操作該物件的成員變數:

// 呼叫成員函式,設定標題和URL
$taobao->setTitle( "淘寶" );
$google->setTitle( "Google 搜尋" );

$taobao->setUrl( 'www.taobao.com' );
$google->setUrl( 'www.google.com' );

// 呼叫成員函式,獲取標題和URL
$taobao->getTitle();
$google->getTitle();

$taobao->getUrl();
$google->getUrl();

1.3 構造&解構函式

1.3.1 建構函式

建構函式是一種特殊的方法。主要用來在建立物件時初始化物件, 即為物件成員變數賦初始值,在建立物件的語句中與 new 運算子一起使用。

PHP 5 允許開發者在一個類中定義一個方法作為建構函式,語法格式如下:void __construct ([ mixed $args [, $... ]] )
在上面的例子中我們就可以透過構造方法來初始化 $url$title 變數:

function __construct( $par1, $par2 ) {
   $this->url = $par1;
   $this->title = $par2;
}

現在我們就不需要再呼叫 setTitlesetUrl 方法了:

$taobao = new Site('www.taobao.com', '淘寶');
$google = new Site('www.google.com', 'Google 搜尋');

// 呼叫成員函式,獲取標題和URL
$taobao->getTitle();
$google->getTitle();

$taobao->getUrl();
$google->getUrl();

1.3.2 解構函式

解構函式(destructor) 與建構函式相反,當物件結束其生命週期時(例如物件所在的函式已呼叫完畢),系統自動執行解構函式。

PHP 5 引入了解構函式的概念,這類似於其它物件導向的語言,其語法格式如下:void __destruct ( void )

<?php
class MyDestructableClass {
   function __construct() {
       print "建構函式\n";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "銷燬 " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass();
?>

1.4 繼承

PHP 使用關鍵字 extends 來繼承一個類,PHP 不支援多繼承,格式如下:

class Child extends Parent {
   // 程式碼部分
}

1.4.1 方法重寫

如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。

1.4.2 父子類初始化順序

如果父類和子類的構造方法和靜態屬性或方法的執行順序遵循以下規則:

靜態屬性和靜態方法的初始化:

  • 靜態屬性和靜態方法在類首次載入(即第一次使用)時初始化。
  • 靜態方法不會自動執行,只有在呼叫時才會執行。
  • 靜態屬性在類載入時初始化,而不是在例項化物件時。

父類構造方法:

  • 當建立子類物件時,如果子類沒有定義自己的構造方法,父類的構造方法會自動呼叫。
  • 如果子類定義了自己的構造方法,父類的構造方法不會自動呼叫,除非子類在其構造方法中顯式呼叫父類構造方法,需要在子類的構造方法中呼叫 parent::__construct()

子類構造方法:

  • 子類的構造方法在物件建立時呼叫,通常在父類構造方法(如果呼叫了)之後執行。

1.4.3 示例說明

<?php
class ParentClass {
    public static $staticProperty = 'Parent Static Property';

    public function __construct() {
        echo "Parent Constructor\n";
    }

    public static function staticMethod() {
        echo "Parent Static Method\n";
    }
}

class ChildClass extends ParentClass {
    public static $childStaticProperty = 'Child Static Property';

    public function __construct() {
        parent::__construct(); // 顯式呼叫父類構造方法
        echo "Child Constructor\n";
    }

    public static function childStaticMethod() {
        echo "Child Static Method\n";
    }
}

// 執行順序演示
ChildClass::staticMethod(); // 觸發靜態方法
ChildClass::childStaticMethod(); // 觸發子類靜態方法
$child = new ChildClass(); // 例項化子類物件
?>
輸出結果

Parent Static Method
Child Static Method
Parent Constructor
Child Constructor

執行順序分析

  • 靜態方法和靜態屬性:
    • ChildClass::staticMethod();:呼叫父類的靜態方法 staticMethod(),此時靜態屬性和方法在類首次載入時初始化。
    • ChildClass::childStaticMethod();:呼叫子類的靜態方法 childStaticMethod()。
  • 父類和子類的構造方法:
    • 當例項化 ChildClass 時,子類的構造方法 __construct 首先呼叫,由於在子類的構造方法中呼叫了 parent::__construct();,因此父類的構造方法 __construct 被顯式呼叫,父類的構造方法先執行,然後才是子類的構造方法。

1.5 訪問控制

PHP 對屬性或方法的訪問控制,是透過在前面新增關鍵字 public(公有),protected(受保護)或 private(私有)來實現的。

  • public(公有):公有的類成員可以在任何地方被訪問。
  • protected(受保護):受保護的類成員則可以被其自身以及其子類和父類訪問。
  • private(私有):私有的類成員則只能被其定義所在的類訪問。

1.5.1 屬性的訪問控制

類屬性必須定義為公有,受保護,私有之一。如果用 var 定義,則被視為公有

<?php
class MyClass
{
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj = new MyClass();
echo $obj->public; // 這行能被正常執行
echo $obj->protected; // 這行會產生一個致命錯誤
echo $obj->private; // 這行也會產生一個致命錯誤
$obj->printHello(); // 輸出 Public、Protected 和 Private


class MyClass2 extends MyClass
{
    // 可以對 public 和 protected 進行重定義,但 private 而不能
    protected $protected = 'Protected2';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj2 = new MyClass2();
echo $obj2->public; // 這行能被正常執行
echo $obj2->private; // 未定義 private
echo $obj2->protected; // 這行會產生一個致命錯誤
$obj2->printHello(); // 輸出 Public、Protected2 和 Undefined

?>

1.5.2 方法的訪問控制

類中的方法可以被定義為公有,私有或受保護。如果沒有設定這些關鍵字,則該方法預設為公有

<?php

class MyClass
{
    // 宣告一個公有的建構函式
    public function __construct() { }

    // 宣告一個公有的方法
    public function MyPublic() { }

    // 宣告一個受保護的方法
    protected function MyProtected() { }

    // 宣告一個私有的方法
    private function MyPrivate() { }

    // 此方法為公有
    function Foo()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate();
    }
}

$myclass = new MyClass;
$myclass->MyPublic(); // 這行能被正常執行
$myclass->MyProtected(); // 這行會產生一個致命錯誤
$myclass->MyPrivate(); // 這行會產生一個致命錯誤
$myclass->Foo(); // 公有,受保護,私有都可以執行



class MyClass2 extends MyClass
{
    // 此方法為公有
    function Foo2()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate(); // 這行會產生一個致命錯誤
    }
}

$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 這行能被正常執行
$myclass2->Foo2(); // 公有的和受保護的都可執行,但私有的不行

class Bar 
{
    public function test() {
        $this->testPrivate();
        $this->testPublic();
    }

    public function testPublic() {
        echo "Bar::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Bar::testPrivate\n";
    }
}

class Foo extends Bar 
{
    public function testPublic() {
        echo "Foo::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Foo::testPrivate\n";
    }
}

$myFoo = new foo();
$myFoo->test(); // Bar::testPrivate 
                // Foo::testPublic
?>

1.6 介面 & 抽象

1.6.1 介面

使用介面(interface),可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容。
介面是透過 interface 關鍵字來定義的,就像定義一個標準的類一樣,但其中定義所有的方法都是空的。
介面中定義的所有方法都必須是公有,這是介面的特性。
要實現一個介面,使用 implements 運算子。類中必須實現介面中定義的所有方法,否則會報一個致命錯誤。類可以實現多個介面,用逗號來分隔多個介面的名稱

<?php
// 宣告一個'iTemplate'介面
interface iTemplate
{
    public function setVariable($name, $var);
    public function getHtml($template);
}


// 實現介面
class Template implements iTemplate
{
    private $vars = array();
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
  
    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
        }
 
        return $template;
    }
}

1.6.2 抽象類

任何一個類,如果它裡面至少有一個方法是被宣告為抽象的,那麼這個類就必須被宣告為抽象的。
定義為抽象的類不能被例項化
被定義為抽象的方法只是宣告瞭其呼叫方式(引數),不能定義其具體的功能實現。
繼承一個抽象類的時候,子類必須定義父類中的所有抽象方法;另外,這些方法的訪問控制必須和父類中一樣(或者更為寬鬆)。例如某個抽象方法被宣告為受保護的,那麼子類中實現的方法就應該宣告為受保護的或者公有的,而不能定義為私有的。

<?php
abstract class AbstractClass
{
 // 強制要求子類定義這些方法
    abstract protected function getValue();
    abstract protected function prefixValue($prefix);

    // 普通方法(非抽象方法)
    public function printOut() {
        print $this->getValue() . PHP_EOL;
    }
}

class ConcreteClass1 extends AbstractClass
{
    protected function getValue() {
        return "ConcreteClass1";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass1";
    }
}

class ConcreteClass2 extends AbstractClass
{
    public function getValue() {
        return "ConcreteClass2";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass2";
    }
}

$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') . PHP_EOL;

$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') . PHP_EOL;
?>

執行以上程式碼,輸出結果為:
ConcreteClass1
FOO_ConcreteClass1
ConcreteClass2
FOO_ConcreteClass2

此外,子類方法可以包含父類抽象方法中不存在的可選引數。
例如,子類定義了一個可選引數,而父類抽象方法的宣告裡沒有,則也是可以正常執行的。

<?php
abstract class AbstractClass
{
    // 我們的抽象方法僅需要定義需要的引數
    abstract protected function prefixName($name);

}

class ConcreteClass extends AbstractClass
{

    // 我們的子類可以定義父類簽名中不存在的可選引數
    public function prefixName($name, $separator = ".") {
        if ($name == "Pacman") {
            $prefix = "Mr";
        } elseif ($name == "Pacwoman") {
            $prefix = "Mrs";
        } else {
            $prefix = "";
        }
        return "{$prefix}{$separator} {$name}";
    }
}

$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";
?>
輸出結果為:
Mr. Pacman
Mrs. Pacwoman

1.7 關鍵字

1.7.1 parent,self,this

大致區別:

  • parent:用於在子類中呼叫父類的成員,尤其是在子類中重寫父類方法後,還想使用父類方法的情況下。
  • self:用於在類的靜態上下文中呼叫當前類的靜態方法和靜態屬性。它與 this 不同,self 不能用於訪問例項屬性或例項方法。
  • this:用於引用當前物件例項,主要在非靜態方法中使用,用來訪問物件的例項屬性和例項方法。

1.7.1.1 parent

parent 關鍵字用於訪問父類中的方法或屬性,特別是在子類中重寫父類的方法時,parent 可以呼叫被覆蓋的父類方法。
常見用法:

  • 呼叫父類的構造方法。
  • 呼叫父類中被子類覆蓋的方法。
class ParentClass {
    public function greet() {
        echo "Hello from Parent!";
    }
}

class ChildClass extends ParentClass {
    public function greet() {
        parent::greet(); // 呼叫父類的方法
        echo " And Hello from Child!";
    }
}

$child = new ChildClass();
$child->greet();
輸出:
Hello from Parent! And Hello from Child!

1.7.1.2 self

self 關鍵字用於引用當前類中的靜態方法靜態屬性。在類的靜態上下文中使用 self 來呼叫當前類的靜態成員。

常見用法:

  • 呼叫當前類的靜態方法。
  • 訪問當前類的靜態屬性。
class MyClass {
    public static $name = "MyClass";

    public static function getName() {
        return self::$name; // 使用 self 呼叫靜態屬性
    }
}

echo MyClass::getName(); // 輸出 "MyClass"

1.7.1.3 this

this 關鍵字用於引用當前物件例項。this 是非靜態上下文中的關鍵字,通常用於呼叫當前物件的例項方法和例項屬性。

常見用法:

  • 訪問當前物件的例項屬性。
  • 呼叫當前物件的例項方法。
class MyClass {
    public $name;

    public function __construct($name) {
        $this->name = $name; // 使用 this 訪問例項屬性
    }

    public function greet() {
        echo "Hello, " . $this->name;
    }
}

$obj = new MyClass("World");
$obj->greet(); // 輸出 "Hello, World"

1.7.2 常量

可以把在類中始終保持不變的值定義為常量。在 定義和使用常量的時候不需要使用 $符號

常量的值必須是一個定值,不能是變數,類屬性,數學運算的結果或函式呼叫。

自 PHP 5.3.0 起,可以用一個變數來動態呼叫類。但該變數的值不能為關鍵字(如 self,parent 或 static)。

<?php
class MyClass
{
    const constant = '常量值';

    function showConstant() {
        echo  self::constant . PHP_EOL;
    }
}

echo MyClass::constant . PHP_EOL;

$classname = "MyClass";
echo $classname::constant . PHP_EOL; // 自 5.3.0 起

$class = new MyClass();
$class->showConstant();

echo $class::constant . PHP_EOL; // 自 PHP 5.3.0 起
?>

1.7.3 Static

宣告類屬性或方法為 static(靜態),就可以不例項化類而直接訪問。
靜態屬性不能透過一個類已例項化的物件來訪問(但靜態方法可以)。
由於靜態方法不需要透過物件即可呼叫,所以偽變數 $this 在靜態方法中不可用。
靜態屬性不可以由物件透過 -> 運算子來訪問,需要雙冒號 ::
自 PHP 5.3.0 起,可以用一個變數來動態呼叫類。但該變數的值不能為關鍵字 self,parent 或 static。

<?php
class Foo {
  public static $my_static = 'foo';
  
  public function staticValue() {
     return self::$my_static;
  }
}

print Foo::$my_static . PHP_EOL;
$foo = new Foo();

print $foo->staticValue() . PHP_EOL;
?>    
執行以上程式,輸出結果為:

foo
foo

1.7.4 Final

PHP 5 新增了一個 final 關鍵字。如果父類中的方法被宣告為 final,則子類無法覆蓋該方法。如果一個類被宣告為 final,則不能被繼承。

以下程式碼執行會報錯:

<?php
class BaseClass {
   public function test() {
       echo "BaseClass::test() called" . PHP_EOL;
   }
   
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called"  . PHP_EOL;
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called"  . PHP_EOL;
   }
}
// 報錯資訊 Fatal error: Cannot override final method BaseClass::moreTesting()
?>

相關文章