首頁 >後端開發 >php教程 >亞型多態性 - 運行時交換實現

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

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原創
2025-02-25 18:15:16612瀏覽

Subtype Polymorphism - Swapping Implementation at Runtime

核心要點

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

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

定義組件的接口和實現

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

<code class="language-php"><?php namespace LibraryCache;

interface CacheInterface
{
    public function set($id, $data);
    public function get($id);
    public function delete($id);
    public function exists($id);
}</code>

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

<code class="language-php"><?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);
    }
}</code>

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

<code class="language-php"><?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);
    }
}</code>

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

<code class="language-php"><?php namespace LibraryCache;

interface CacheInterface
{
    public function set($id, $data);
    public function get($id);
    public function delete($id);
    public function exists($id);
}</code>
<code class="language-php"><?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);
    }
}</code>
<code class="language-php"><?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);
    }
}</code>

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

將緩存驅動程序投入使用

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

<code class="language-php"><?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);
}</code>
<code class="language-php"><?php namespace LibraryCache;

class FileCache extends AbstractCache
{
    // the same implementation goes here
}</code>

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

<code class="language-php"><?php namespace LibraryCache;

class ApcCache extends AbstractCache
{
    // the same implementation goes here 
}</code>

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

<code class="language-php"><?php namespace LibraryView;

interface ViewInterface
{
    public function setTemplate($template);
    public function __set($field, $value);
    public function __get($field);
    public function render();
}</code>

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

<code class="language-php"><?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());
        } 
    }
}</code>

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

結束語

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