PHP 物件導向基礎概念

Galois發表於2020-01-14

物件導向簡介

在物件導向的程式設計(英語:Object-oriented programming,縮寫:OOP)中,物件是一個由資訊及對資訊進行處理的描述所組成的整體,是對現實世界的抽象。
在現實世界裡我們所面對的事情都是物件,如計算機、電視機、腳踏車等。

物件導向專業名詞介紹
物件導向概念中的術語說明
定義類一件事物的抽象特點,類的定義包含了資料的形式以及對資料的操作。
物件 是類的例項。
成員變數 定義在類內部的變數。該變數的值對外是不可見的,但是可以透過成員函式訪問,在類被例項化為物件後,該變數即可成為物件的屬性。
成員函式 定義在類的內部,可用於訪問物件的資料。
繼承 繼承性是子類自動共享父類資料結構和方法的機制,這是類之間的一種關係。在定義和實現一個類的時候,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作為自己的內容,並加入若干新的內容。
父類 一個類被其他類繼承,可將該類稱為父類,或基類,或超類。
子類 一個類繼承其他類稱為子類,也可稱為派生類。
多型 多型性是指相同的函式或方法可作用於多種型別的物件上並獲得不同的結果。不同的物件,收到同一訊息可以產生不同的結果,這種現象稱為多型性。
過載 簡單說,就是函式或者方法有同樣的名稱,但是引數列表不相同的情形,這樣的同名不同引數的函式或者方法之間,互相稱之為過載函式或者方法。
抽象性 抽象性是指將具有一致的資料結構(屬性)和行為(操作)的物件抽象成類。一個類就是這樣一種抽象,它反映了與應用有關的重要性質,而忽略其他一些無關內容。任何類的劃分都是主觀的,但必須與具體的應用有關。
封裝 封裝是指將現實世界中存在的某個客體的屬性與行為繫結在一起,並放置在一個邏輯單元內。
建構函式 主要用來在建立物件時初始化物件, 即為物件成員變數賦初始值,總與new運算子一起使用在建立物件的語句中。
解構函式 解構函式(destructor) 與建構函式相反,當物件結束其生命週期時(例如物件所在的函式已呼叫完畢),系統自動執行解構函式。解構函式往往用來做"清理善後" 的工作(例如在建立物件時用new開闢了一片記憶體空間,應在退出前在解構函式中用delete釋放)。

透過 Car 類建立三個物件:car1,car2,car3。

$car1 = new Car ();
$car2 = new Car ();
$car3 = new Car ();

PHP 類定義

PHP 定義類的一般語法:

<?php
class phpClass {
  var $var1;
  var $var2 = "constant string";
  function myfunc ($arg1, $arg2) {
     [..]
  }
  [..]
}

程式碼解析:

  • 類使用 class 關鍵字後加上類名定義。
  • 類名後的一對大括號內可以定義變數和方法。
  • 類的變數使用 var 來宣告,變數也可以初始化值。
  • 函式定義類似 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;
  }
}

變數 $this 代表自身的物件。PHP_EOL 為換行符。

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

$learnku = new Site;
$laravel = new Site;
$google = new Site;

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

呼叫成員方法

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

// 呼叫成員函式,設定標題和URL
$learnku->setTitle( "駭客學習社群" );
$laravel->setTitle( "PHP-Web 開源框架" );
$google->setTitle( "Google 搜尋" );

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

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

$learnku->getUrl();
$laravel->getUrl();
$google->getUrl();

完整例項程式碼:

<?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;
  }
}

$learnku = new Site;
$laravel = new Site;
$google = new Site;

// 呼叫成員函式,設定標題和URL
$learnku->setTitle( "駭客學習社群" );
$laravel->setTitle( "PHP-Web 開源框架" );
$google->setTitle( "Google 搜尋" );

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

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

$learnku->getUrl();
$laravel->getUrl();
$google->getUrl();

執行以上程式碼,輸出結果為:

駭客學習社群
PHP-Web 開源框架
Google 搜尋
www.learnku.com
www.laravel.com
www.google.com

PHP 建構函式

建構函式是一種特殊的方法,主要用來在建立物件時初始化物件, 即為物件成員變數賦初始值,在建立物件的語句中與 new 運算子一起使用。
PHP 5 允許開發者在一個類中定義一個方法作為建構函式,語法格式如下:

void __construct ([ mixed $args [, $... ]] )

構造方法來初始化 $url 和 $title 變數:

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

現在我們就不需要再呼叫 setTitle 和 setUrl 方法了:

$learnku = new Site('www.learnku.com', '駭客學習社群');
$laravel = new Site('www.laravel.com', 'PHP-Web 開源框架');
$google = new Site('www.google.com', 'Google 搜尋');

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

$learnku->getUrl();
$laravel->getUrl();
$google->getUrl();

解構函式

解構函式(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();

執行後輸出結果:

建構函式
銷燬 MyDestructableClass

繼承

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

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

例項中 Child_Site 類繼承類 Site 類,並擴充了功能:

<?php 
// 子類擴充套件站點類別
class Child_Site extends Site {
   var $category;

    function setCate($par){
        $this->category = $par;
    }

    function getCate(){
        echo $this->category . PHP_EOL;
    }

方法重寫

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

class Child_Site extends Site {
    function getUrl()
    {
       echo $this->url . PHP_EOL;
       return $this->url;  // 新加入的功能
    }
    function getTitle()
    {
        echo $this->title . PHP_EOL;
        return $this->title;  // 新加入的功能
    }

訪問控制

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

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

屬性的訪問控制

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

<?php
/**
 * Define MyClass
 */
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

/**
 * Define MyClass2
 */
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

方法的訪問控制

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

<?php
/**
 * Define MyClass
 */
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();  // 公有,受保護,私有都可以執行

/**
 * Define MyClass2
 */
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

介面

使用介面(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;
    }
}

常量

可以把在類中始終保持不變的值定義為常量。在定義和使用常量的時候不需要使用 $ 符號。
常量的值必須是一個定值,不能是變數,類屬性,數學運算的結果或函式呼叫。

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

<?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 起

抽象類

任何一個類,如果它裡面至少有一個方法是被宣告為抽象的,那麼這個類就必須被宣告為抽象的。

定義為抽象的類不能被例項化。

被定義為抽象的方法只是宣告瞭其呼叫方式(引數),不能定義其具體的功能實現。

繼承一個抽象類的時候,子類必須定義父類中的所有抽象方法;另外,這些方法的訪問控制必須和父類中一樣(或者更為寬鬆)。例如某個抽象方法被宣告為受保護的,那麼子類中實現的方法就應該宣告為受保護的或者公有的,而不能定義為私有的。

<?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

Static 關鍵字

宣告類屬性或方法為 static(靜態),就可以不例項化類而直接訪問。

靜態屬性不能透過一個類已例項化的物件來訪問(但靜態方法可以)。

由於靜態方法不需要透過物件即可呼叫,所以 偽變數 $this 在靜態方法中不可用。

靜態屬性不可以由物件透過 -> 運算子來訪問。

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

<?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

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()

呼叫父類構造方法 Parent

PHP 不會在子類的構造方法中自動的呼叫父類的構造方法。要 執行父類的構造方法,需要在子類的構造方法中呼叫 parent::__construct() 。

<?php
class BaseClass {
   function __construct() {
       print "BaseClass 類中構造方法" . PHP_EOL;
   }
}
class SubClass extends BaseClass {
   function __construct() {
       parent::__construct();  // 子類構造方法不能自動呼叫父類的構造方法
       print "SubClass 類中構造方法" . PHP_EOL;
   }
}
class OtherSubClass extends BaseClass {
    // 繼承 BaseClass 的構造方法
}

// 呼叫 BaseClass 構造方法
$obj = new BaseClass();

// 呼叫 BaseClass、SubClass 構造方法
$obj = new SubClass();

// 呼叫 BaseClass 構造方法
$obj = new OtherSubClass();

輸出結果:

BaseClass 類中構造方法
BaseClass 類中構造方法
SubClass 類中構造方法
BaseClass 類中構造方法
本作品採用《CC 協議》,轉載必須註明作者和本文連結
不要試圖用百米衝刺的方法完成馬拉松比賽。

相關文章