目錄
子類型多態性和參數多態性的主要區別是什麼?
子類型多態性在 Java 中是如何工作的?
你能提供子類型多態性的一個例子嗎?
子類型多態性在編程中的意義是什麼?
子類型多態性與 Liskov 替換原則有什麼關係?
所有編程語言都支持子類型多態性嗎?
靜態多態性和動態多態性有什麼區別?
你能解釋子類型多態性中向上轉換的概念嗎?
在子類型多態性的上下文中,向下轉換是什麼?
子類型多態性如何促進代碼的可重用性?
首頁 後端開發 php教程 亞型多態性 - 運行時交換實現

亞型多態性 - 運行時交換實現

Feb 25, 2025 pm 06:15 PM

Subtype Polymorphism - Swapping Implementation at Runtime

核心要點

  • 面向對象設計中的子類型多態性是指系統定義一組契約或接口,然後由不同的子類型實現這些契約或接口的能力。這對於設計可擴展的系統至關重要,這些系統可以消費特定的契約,而無需檢查實現者是否屬於預期的類型。
  • 本文通過開發一個可插入的緩存組件來演示子類型多態性的使用,該組件可以通過開發額外的緩存驅動程序來擴展以適應用戶的需求。
  • 緩存組件的一個關鍵特性是它能夠在運行時交換不同的緩存驅動程序,而無需更改任何客戶端代碼。這是通過定義一個緩存契約實現的,該契約隨後由不同的實現來遵守,從而利用了多態性的優勢。
  • 緩存組件可以在運行時切換後端,這突出了多態性在設計高度解耦模塊中的重要性。這允許在運行時輕鬆重新連接,而不會導致系統其他部分出現脆弱性或剛性相關問題。
  • 子類型多態性不僅使系統更正交、更容易擴展,而且不易違反開放/封閉原則和“面向接口編程”原則等核心範例。它是面向對象編程的一個基本方面,允許代碼的靈活性和可重用性。

許多人可能懷疑繼承和多態性在面向對象設計中的相關性嗎?可能很少,大多數可能是由於無知或思維狹隘。但這裡有一個小問題不容忽視。雖然理解繼承的邏輯很簡單,但在深入研究多態性的細節時,事情就變得更加困難了。 “多態性”這個術語本身就令人望而生畏,其學術定義充滿了多種不同的觀點,這使得更難以理解其背後的實際內容。諸如參數多態性和特設多態性之類的周邊概念(通常通過方法覆蓋/重載實現)在某些編程語言中確實有其顯著的應用領域,但在設計能夠消費特定契約(讀取抽象)的可擴展系統時,應該放棄最後一種情況,而無需檢查實現者是否屬於預期的類型。簡而言之,大多數時候,在面向對象編程中對多態性的任何通用引用都被隱含地認為是系統公開的一種能力,該能力用於定義一組契約或接口,而這些契約或接口又由不同的實現來遵守。這種“規範的”多態性通常被稱為子類型多態性,因為接口的實現者被認為是它們的子類型,無論是否存在實際的層次結構。正如人們可能預期的那樣,理解多態性的本質只是學習過程的一半;另一半當然是演示如何設計多態系統,使其能夠適應相當現實的情況,而不會陷入僅僅展示“一些漂亮的教學代碼”(在許多情況下,這是玩具代碼的廉價委婉說法)的陷阱。在本文中,我將向您展示如何通過開發一個可插入的緩存組件來利用多態性提供的優點。核心功能以後可以擴展以滿足您的需求,方法是開發額外的緩存驅動程序。

定義組件的接口和實現

構建可擴展緩存組件時,可供選擇的選項菜單絕非匱乏(如果您對此表示懷疑,只需看看一些流行框架背後的情況)。但是,在這裡,我提供的組件具有在運行時交換不同緩存驅動程序的巧妙能力,而無需修改任何客戶端代碼。那麼,如何在開發過程中不費太多力氣就能做到這一點呢?嗯,第一步應該是……是的,定義一個隔離的緩存契約,稍後將由不同的實現來遵守,從而利用多態性的好處。在其最基本的層面上,上述契約如下所示:

<?php namespace LibraryCache;

interface CacheInterface
{
    public function set($id, $data);
    public function get($id);
    public function delete($id);
    public function exists($id);
}
登入後複製
登入後複製

CacheInterface 接口是一個骨架契約,它抽象了通用緩存元素的行為。有了接口,就可以輕鬆創建一些符合其契約的具體緩存實現。由於我想保持簡潔易懂,我設置的緩存驅動程序將只是一個精簡的二重奏:第一個使用文件系統作為緩存/獲取數據的底層後端,而第二個在幕後使用 APC 擴展。以下是基於文件的緩存實現:

<?php namespace LibraryCache;

class FileCache implements CacheInterface
{
    const DEFAULT_CACHE_DIRECTORY = 'Cache/';
    private $cacheDir;

    public function __construct($cacheDir = self::DEFAULT_CACHE_DIRECTORY) {
        $this->setCacheDir($cacheDir);
    }

    public function setCacheDir($cacheDir) {
        if (!is_dir($cacheDir)) {
            if (!mkdir($cacheDir, 0644)) {
                throw InvalidArgumentException('The cache directory is invalid.');
            }
        }
        $this->cacheDir = $cacheDir;
        return $this;
    }

    public function set($id, $data) {
        if (!file_put_contents($this->cacheDir . $id, serialize($data), LOCK_EX)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
        return $this;
    }

    public function get($id) {
        if (!$data = unserialize(@file_get_contents($this->cacheDir . $id, false))) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        }
        return $data;
    }

    public function delete($id) {
        if (!@unlink($this->cacheDir . $id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
        return $this;
    }

    public function exists($id) {
        return file_exists($this->cacheDir . $id);
    }
}
登入後複製
登入後複製

FileCache 類的驅動邏輯應該很容易理解。到目前為止,這裡最相關的事情是它公開了一種整潔的多態行為,因為它忠實地實現了早期的 CacheInterface。雖然這種能力很甜蜜迷人,但就其本身而言,考慮到這裡的目標是創建一個能夠在運行時切換後端的緩存組件,我不會為此大加讚賞。讓我們為了教學目的而付出額外的努力,並使 CacheInterface 的另一個精簡實現栩栩如生。下面的實現遵守接口的契約,但這次是通過使用 APC 擴展捆綁的方法:

<?php namespace LibraryCache;

class ApcCache implements CacheInterface
{
    public function set($id, $data, $lifeTime = 0) {
        if (!apc_store($id, $data, (int) $lifeTime)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
    }

    public function get($id) {
        if (!$data = apc_fetch($id)) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        } 
        return $data;
    }

    public function delete($id) {
        if (!apc_delete($id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
    }

    public function exists($id) {
        return apc_exists($id);
    }
}
登入後複製
登入後複製

ApcCache 類不是您在職業生涯中見過的最炫的 APC 包裝器,它打包了從內存中保存、檢索和刪除數據所需的所有功能。讓我們為我們自己鼓掌,因為我們已經成功地實現了一個輕量級緩存模塊,其具體的後台不僅可以由於其多態性而在運行時輕鬆交換,而且以後添加更多後台也極其簡單。只需編寫另一個符合 CacheInterface 的實現即可。但是,我應該強調的是,實際的子類型多態性是通過實現通過接口構造定義的契約來實現的,這是一種非常普遍的方法。但是,沒有什麼可以阻止您不那麼正統,並通過切換一個聲明為一組抽象方法的接口(位於抽像類中)來獲得相同的結果。如果您感覺冒險並想走那條旁路,則可以如下重構契約和相應的實現:

<?php namespace LibraryCache;

interface CacheInterface
{
    public function set($id, $data);
    public function get($id);
    public function delete($id);
    public function exists($id);
}
登入後複製
登入後複製
<?php namespace LibraryCache;

class FileCache implements CacheInterface
{
    const DEFAULT_CACHE_DIRECTORY = 'Cache/';
    private $cacheDir;

    public function __construct($cacheDir = self::DEFAULT_CACHE_DIRECTORY) {
        $this->setCacheDir($cacheDir);
    }

    public function setCacheDir($cacheDir) {
        if (!is_dir($cacheDir)) {
            if (!mkdir($cacheDir, 0644)) {
                throw InvalidArgumentException('The cache directory is invalid.');
            }
        }
        $this->cacheDir = $cacheDir;
        return $this;
    }

    public function set($id, $data) {
        if (!file_put_contents($this->cacheDir . $id, serialize($data), LOCK_EX)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
        return $this;
    }

    public function get($id) {
        if (!$data = unserialize(@file_get_contents($this->cacheDir . $id, false))) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        }
        return $data;
    }

    public function delete($id) {
        if (!@unlink($this->cacheDir . $id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
        return $this;
    }

    public function exists($id) {
        return file_exists($this->cacheDir . $id);
    }
}
登入後複製
登入後複製
<?php namespace LibraryCache;

class ApcCache implements CacheInterface
{
    public function set($id, $data, $lifeTime = 0) {
        if (!apc_store($id, $data, (int) $lifeTime)) {
            throw new RuntimeException("Unable to cache the data with ID '$id'.");
        }
    }

    public function get($id) {
        if (!$data = apc_fetch($id)) {
            throw new RuntimeException("Unable to get the data with ID '$id'.");
        } 
        return $data;
    }

    public function delete($id) {
        if (!apc_delete($id)) {
            throw new RuntimeException("Unable to delete the data with ID '$id'.");
        }
    }

    public function exists($id) {
        return apc_exists($id);
    }
}
登入後複製
登入後複製

從上到下,這確實是一種多態方法,它與之前討論的方法針鋒相對。就我個人而言,這只是我個人的說法,我更喜歡使用接口構造來定義契約,並且只在封裝幾個子類型共享的樣板實現時才使用抽像類。您可以選擇最適合您需求的方法。在這一點上,我可以放下帷幕,寫一些花哨的結束評論,誇誇我們令人印象深刻的編碼技巧,並吹噓我們緩存組件的靈活性,但這將是對我們的怠慢。當存在一些能夠消費多個實現的客戶端代碼時,多態性會展現出其最具誘惑力的方面,而無需檢查這些實現是否是某種類型的實例,只要它們符合預期的契約即可。因此,讓我們通過將緩存組件連接到一個基本的客戶端視圖類來揭示該方面,這將允許我們毫不費力地進行一些整潔的 HTML 緩存。

將緩存驅動程序投入使用

通過我們的示例緩存模塊緩存 HTML 輸出非常簡單,我將在其他時間保存任何冗長的解釋。整個緩存過程可以簡化為一個簡單的視圖類,類似於以下這個:

<?php namespace LibraryCache;

abstract class AbstractCache
{
    abstract public function set($id, $data);
    abstract public function get($id);
    abstract public function delete($id);
    abstract public function exists($id);
}
登入後複製
<?php namespace LibraryCache;

class FileCache extends AbstractCache
{
    // the same implementation goes here
}
登入後複製

最炫的傢伙是類的構造函數,它使用早期的 CacheInterface 的實現者,以及 render() 方法。由於最後一個方法的職責是在視圖的模板被推送到輸出緩衝區後緩存它,因此利用此能力並緩存整個 HTML 文檔會非常不錯。假設視圖的默認模板具有以下結構:

<?php namespace LibraryCache;

class ApcCache extends AbstractCache
{
    // the same implementation goes here 
}
登入後複製

現在,讓我們玩得開心一些,通過向視圖提供 ApcCache 類的實例來緩存文檔:

<?php namespace LibraryView;

interface ViewInterface
{
    public function setTemplate($template);
    public function __set($field, $value);
    public function __get($field);
    public function render();
}
登入後複製

很不錯,對吧?但是等等!我太興奮了,忘記提到上面的代碼片段會在任何未安裝 APC 擴展的系統上爆炸(調皮的系統管理員!)。這是否意味著精心製作的緩存模塊不再可重用?這正是基於文件的驅動程序發揮作用的地方,它可以放入客戶端代碼中而不會收到任何投訴:

<?php namespace LibraryView;
use LibraryCacheCacheInterface;

class View implements ViewInterface
{
    const DEFAULT_TEMPLATE = 'default';    
    private $template;
    private $fields = array();
    private $cache;

    public function __construct(CacheInterface $cache, $template = self::DEFAULT_TEMPLATE) {
        $this->cache = $cache;
        $this->setTemplate($template);
    }

    public function setTemplate($template) {
        $template = $template . '.php';
        if (!is_file($template) || !is_readable($template)) {
            throw new InvalidArgumentException(
                "The template '$template' is invalid.");   
        }
        $this->template = $template;
        return $this;
    }

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

    public function __get($name) {
        if (!isset($this->fields[$name])) {
            throw new InvalidArgumentException(
                "Unable to get the field '$field'.");
        }
        return $this->fields[$name];
    }

    public function render() {
        try {
            if (!$this->cache->exists($this->template)) {
                extract($this->fields);
                ob_start();
                include $this->template;
                $this->cache->set($this->template, ob_get_clean());
            }
            return $this->cache->get($this->template);
        }
        catch (RuntimeException $e) {
            throw new Exception($e->getMessage());
        } 
    }
}
登入後複製

上面的單行代碼明確聲明視圖將使用文件系統而不是共享內存來緩存其輸出。這種動態切換緩存後端簡明地說明了為什麼多態性在設計高度解耦的模塊時如此重要。它允許我們在運行時輕鬆地重新連接事物,而不會將脆弱性/剛性相關的偽影傳播到我們系統的其他部分。

結束語

在使理解該概念變得難以捉摸的大量正式定義的壓迫下,多態性確實是生活中那些美好的事物之一,一旦您理解了它,就會讓您想知道您如何在沒有它的情況下繼續這麼長時間。多態系統本質上更正交、更容易擴展,並且不太容易違反開放/封閉原則和明智的“面向接口編程”原則等核心範例。儘管相當原始,但我們的緩存模塊是這些優點的突出示例。如果您尚未重構您的應用程序以利用多態性帶來的好處,那麼您最好快點,因為您錯過了大獎! 圖片來自 Fotolia

關於子類型多態性的常見問題解答 (FAQ)

子類型多態性和參數多態性的主要區別是什麼?

子類型多態性,也稱為包含多態性,是一種多態性形式,其中一個名稱表示許多不同類別的實例,這些類別通過某個公共超類相關聯。另一方面,參數多態性允許函數或數據類型以相同的方式處理值,而無需依賴其類型。參數多態性是一種使語言更具表達力同時保持完全靜態類型安全性的方法。

子類型多態性在 Java 中是如何工作的?

在 Java 中,子類型多態性是通過使用繼承和接口來實現的。超類引用變量可以指向子類對象。這允許 Java 在運行時決定調用哪個方法,這被稱為動態方法調度。它是 Java 的強大功能之一,使它能夠支持動態多態性。

你能提供子類型多態性的一個例子嗎?

當然,讓我們考慮一下 Java 中的一個簡單示例。假設我們有一個名為“Animal”的超類和兩個子類“Dog”和“Cat”。 “Dog”和“Cat”類都重寫了“Animal”類的“sound”方法。現在,如果我們創建一個“Animal”引用指向“Dog”或“Cat”對象並調用“sound”方法,Java 將在運行時決定調用哪個類的“sound”方法。這是一個子類型多態性的例子。

子類型多態性在編程中的意義是什麼?

子類型多態性是面向對象編程的一個基本方面。它允許代碼的靈活性和可重用性。使用子類型多態性,您可以為一組類設計一個通用接口,然後使用此接口以統一的方式與這些類的對象交互。這將導致更簡潔、更直觀和更易於維護的代碼。

子類型多態性與 Liskov 替換原則有什麼關係?

Liskov 替換原則 (LSP) 是面向對象設計的一個原則,它指出,如果程序正在使用基類,則它應該能夠使用任何其子類,而無需程序知道它。換句話說,超類的對象應該能夠被子類的對象替換,而不會影響程序的正確性。子類型多態性是 LSP 的直接應用。

所有編程語言都支持子類型多態性嗎?

不,並非所有編程語言都支持子類型多態性。它主要是靜態類型面向對象編程語言(如 Java、C 和 C#)的一個特性。像 Python 和 JavaScript 這樣的動態類型語言具有不同形式的多態性,稱為鴨子類型。

靜態多態性和動態多態性有什麼區別?

靜態多態性,也稱為編譯時多態性,是通過方法重載實現的。關於調用哪個方法的決定是在編譯時做出的。另一方面,動態多態性,也稱為運行時多態性,是通過方法重寫實現的。關於調用哪個方法的決定是在運行時做出的。子類型多態性是一種動態多態性。

你能解釋子類型多態性中向上轉換的概念嗎?

向上轉換是將派生類對象視為基類對象的過程。它是子類型多態性的一個關鍵方面。當您向上轉換派生類對象時,您可以調用基類中定義的任何方法。但是,如果該方法在派生類中被重寫,則將調用重寫版本。

在子類型多態性的上下文中,向下轉換是什麼?

向下轉換與向上轉換相反。它是將超類對象轉換為子類的過程。當您需要訪問僅存在於子類中的方法時,可以使用向下轉換。但是,向下轉換可能很危險,因為它如果被轉換的對象實際上不具有您要轉換到的類型,則可能導致 ClassCastException。

子類型多態性如何促進代碼的可重用性?

子類型多態性允許我們編寫更通用和可重用的代碼。通過使用超類引用來與子類對象交互,我們可以編寫適用於各種對象的代碼,只要它們都屬於同一個超類的子類即可。這意味著我們可以添加新的子類,而無需更改使用超類的代碼,這使得我們的代碼更靈活、更容易維護。

以上是亞型多態性 - 運行時交換實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1658
14
CakePHP 教程
1415
52
Laravel 教程
1309
25
PHP教程
1257
29
C# 教程
1231
24
會話如何劫持工作,如何在PHP中減輕它? 會話如何劫持工作,如何在PHP中減輕它? Apr 06, 2025 am 12:02 AM

會話劫持可以通過以下步驟實現:1.獲取會話ID,2.使用會話ID,3.保持會話活躍。在PHP中防範會話劫持的方法包括:1.使用session_regenerate_id()函數重新生成會話ID,2.通過數據庫存儲會話數據,3.確保所有會話數據通過HTTPS傳輸。

說明PHP中的不同錯誤類型(注意,警告,致命錯誤,解析錯誤)。 說明PHP中的不同錯誤類型(注意,警告,致命錯誤,解析錯誤)。 Apr 08, 2025 am 12:03 AM

PHP中有四種主要錯誤類型:1.Notice:最輕微,不會中斷程序,如訪問未定義變量;2.Warning:比Notice嚴重,不會終止程序,如包含不存在文件;3.FatalError:最嚴重,會終止程序,如調用不存在函數;4.ParseError:語法錯誤,會阻止程序執行,如忘記添加結束標籤。

PHP和Python:比較兩種流行的編程語言 PHP和Python:比較兩種流行的編程語言 Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

說明PHP中的安全密碼散列(例如,password_hash,password_verify)。為什麼不使用MD5或SHA1? 說明PHP中的安全密碼散列(例如,password_hash,password_verify)。為什麼不使用MD5或SHA1? Apr 17, 2025 am 12:06 AM

在PHP中,應使用password_hash和password_verify函數實現安全的密碼哈希處理,不應使用MD5或SHA1。1)password_hash生成包含鹽值的哈希,增強安全性。 2)password_verify驗證密碼,通過比較哈希值確保安全。 3)MD5和SHA1易受攻擊且缺乏鹽值,不適合現代密碼安全。

什麼是HTTP請求方法(獲取,發布,放置,刪除等),何時應該使用? 什麼是HTTP請求方法(獲取,發布,放置,刪除等),何時應該使用? Apr 09, 2025 am 12:09 AM

HTTP請求方法包括GET、POST、PUT和DELETE,分別用於獲取、提交、更新和刪除資源。 1.GET方法用於獲取資源,適用於讀取操作。 2.POST方法用於提交數據,常用於創建新資源。 3.PUT方法用於更新資源,適用於完整更新。 4.DELETE方法用於刪除資源,適用於刪除操作。

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP行動:現實世界中的示例和應用程序 PHP行動:現實世界中的示例和應用程序 Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

解釋PHP 7.4中引入的箭頭功能(短閉合)。 解釋PHP 7.4中引入的箭頭功能(短閉合)。 Apr 06, 2025 am 12:01 AM

箭頭函數在PHP7.4中引入,是短閉包的簡化形式。 1)它們使用=>運算符定義,省略function和use關鍵字。 2)箭頭函數自動捕獲當前作用域變量,無需use關鍵字。 3)它們常用於回調函數和短小計算,提高代碼簡潔性和可讀性。

See all articles