首頁 >後端開發 >php教程 >虛擬代理的介紹,第2部分

虛擬代理的介紹,第2部分

William Shakespeare
William Shakespeare原創
2025-02-27 10:41:13707瀏覽

An Intro to Virtual Proxies, Part 2

核心要點

  • 基於多態性的虛擬代理允許延遲構建/加載代價高昂的對像圖,而無需修改客戶端代碼。
  • 代理可以設計為與單個對像或對象集合一起工作,從而在管理數據方面提供靈活性和效率。
  • 虛擬代理在延遲加載存儲中的領域對象集合(例如,一批博客文章,其中相關評論可以按需從數據庫中提取)方面特別有效。
  • 通過數據映射器實現模擬真實評論集合行為的代理,從而提高持久性無關性。
  • 虛擬代理是延遲執行代價高昂的任務(例如,延遲加載存儲層中的大量數據)並減少面向對象應用程序中常見剛性和脆弱性問題的有效工具。

虛擬代理這個名稱聽起來很花哨,但它可能是“面向接口編程”理念不僅僅是一個枯燥教條原則的最顯著例子之一。虛擬代理基於多態性(動態多態性,而不是通過簡單的方法覆蓋實現的臨時多態性),是一個簡單而可靠的概念,它允許您延遲構建/加載代價高昂的對像圖,而無需修改客戶端代碼。代理的一大優點是,它們在概念上可以設計為與單個對像或對象集合一起工作(或者兩者兼而有之,儘管這樣做可能會危及關注點分離,並且隨著時間的推移難以管理)。為了從實踐的角度演示如何利用虛擬代理提供的功能,在本系列的第一部分中,我介紹了幾個示例的開發過程,展示瞭如何使用基本代理從數據庫中提取聚合以滿足簡單的領域模型。雖然經驗可能是有益的,而且幸運的是也很有趣,但它的另一方面有點具有欺騙性,因為它展示了虛擬代理的來龍去脈,但沒有展示如何在更現實的場景中實現它們。在延遲加載存儲中的領域對象集合方面,代理是無與倫比的。要了解這個概念,只需考慮一批博客文章,其中每一組相關評論都可以按需從數據庫中提取;您一定會明白為什麼代理在這種情況下能夠出色地實現其承諾。

通常情況下,實踐是最好的老師。在本部分中,我將展示如何將代理連接到特定領域對象的集合。我將在非常基礎的層面上重現這個典型的場景,這樣您就可以輕鬆地了解其驅動邏輯。

創建領域對象集合

如前所述,在從持久層獲取與底層領域對象集合綁定的聚合根時,通常會創建虛擬代理。由於集合的本質特性,在許多情況下,集合的預先設置代價很高,這使得它們成為按需加載以減少數據庫往返開銷的良好候選對象。此外,考慮到博客文章與其對應評論之間的“一對多”關聯在本用例中相當忠實地反映出來,在處理具體的代理之前,首先通過一些簡單的領域類來建模這種關係將很有指導意義。為了使事情易於理解,我將在測試階段添加的兩個參與者將是一個隔離的接口和一個基本的實現者。這兩個結合在一起,將定義泛型博客文章對象的契約和實現:

<code class="language-php"><?php namespace Model;
use ModelCollectionCommentCollectionInterface;

interface PostInterface
{
    public function setId($id);
    public function getId();

    public function setTitle($title);
    public function getTitle();

    public function setContent($content);
    public function getContent();

    public function setComments(CommentCollectionInterface $comments);
    public function getComments();
}</code>
<code class="language-php"><?php namespace Model;
    use ModelCollectionCommentCollectionInterface;

class Post implements PostInterface
{
    protected $id;
    protected $title;
    protected $content;
    protected $comments;

    public function __construct($title, $content, CommentCollectionInterface $comments = null)  {
        $this->setTitle($title);
        $this->setContent($content);
        $this->comments = $comments;
    }

    // ... (Post class methods remain largely unchanged) ...
}</code>

理解上述Post類的邏輯是一個簡單的過程,不需要真正解釋。儘管如此,這裡有一個值得注意的相關細節:該類在構造函數中明確聲明了對尚未定義的評論集合的依賴關係。現在讓我們創建生成帖子評論的類:

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

interface CommentInterface
{
     public function setId($id);
     public function getId();

     public function setContent($content);
     public function getContent();

     public function setPoster($poster);
     public function getPoster();
}</code>
<code class="language-php"><?php namespace Model;

class Comment implements CommentInterface
{
    protected $id;
    protected $content;
    protected $poster;

    public function __construct($content, $poster) {
        $this->setContent($content);
        $this->setPoster($poster);
    }

    // ... (Comment class methods remain largely unchanged) ...
}</code>

到目前為止,一切進展順利。除了它們是基本領域模型的精簡塊之外,關於上述領域類沒有什麼好說的,其中每個博客文章對像都公開與相關評論的“一對多”關聯。如果您願意,可以稱我為純粹主義者,但在我看來,如果模型的當前實現沒有用評論集合來增強,它看起來就顯得不完整且笨拙。讓我們通過向其中添加此額外組件的邏輯來使模型更豐富:

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

interface CommentCollectionInterface extends Countable, IteratorAggregate
{
    public function getComments();
}</code>
<code class="language-php"><?php namespace ModelCollection;
use ModelCommentInterface;

class CommentCollection implements CommentCollectionInterface
{
    protected $comments = array();

    public function __construct(array $comments = array()) {
        // ... (CommentCollection class methods remain largely unchanged) ...
    }
}</code>

如果您觀察仔細並瀏覽CommentCollection類,您首先會注意到它只不過是一個隱藏在華麗偽裝背後的可迭代、可計數的數組包裝器。事實上,數組集合有多種形式和風格,但大多數時候它們只是Iterator和ArrayAccess SPL類的簡單用法。在本例中,我想讓自己(和您)免於處理如此枯燥的任務,並將該類設為IteratorAggregate的實現者。有了評論集合之後,我們可以更進一步,讓領域模型做它應該做的事情——在一些博客文章對像上進行操作,甚至將它們與從數據庫中急切地獲取的一批評論互連。但是這樣做只會欺騙我們自己,而不會充分利用虛擬代理提供的功能。考慮到在典型的實現中,代理公開了與實際領域對象相同的API,與之前的CommentCollection類交互的代理也應該實現CommentCollectionInterface,以便在不引入一堆有問題的條件語句的情況下遵守與客戶端代碼的契約。

通過虛擬代理與領域對象的集合交互

坦率地說,像前面提到的那樣包裝數組的集合可以獨立存在,而無需依賴任何其他依賴項。 (如果您持懷疑態度,可以隨意檢查Doctrine中的集合在幕後如何工作。)儘管如此,請記住,我試圖實現一個模擬真實評論集合行為的代理,但實際上它是一個輕量級的替代品。需要問的問題是:如何從數據庫中提取評論並將其放入之前的集合中?有幾種方法可以實現這一點,但我認為最吸引人的一種是通過數據映射器,因為它提高了持久性無關性。下面的映射器可以很好地從存儲中獲取評論集合。請查看:

<code class="language-php"><?php namespace Model;
use ModelCollectionCommentCollectionInterface;

interface PostInterface
{
    public function setId($id);
    public function getId();

    public function setTitle($title);
    public function getTitle();

    public function setContent($content);
    public function getContent();

    public function setComments(CommentCollectionInterface $comments);
    public function getComments();
}</code>
<code class="language-php"><?php namespace Model;
    use ModelCollectionCommentCollectionInterface;

class Post implements PostInterface
{
    protected $id;
    protected $title;
    protected $content;
    protected $comments;

    public function __construct($title, $content, CommentCollectionInterface $comments = null)  {
        $this->setTitle($title);
        $this->setContent($content);
        $this->comments = $comments;
    }

    // ... (Post class methods remain largely unchanged) ...
}</code>

雖然CommentMapper類公開的查找器通常堅持在標準數據映射器實現中可能期望的API,但到目前為止,fetchAll()方法是其中最引人注目的方法。它首先從存儲中提取所有博客文章評論並將它們放入集合中,最後將集合返回給客戶端代碼。如果您像我一樣,您的腦海中可能會響起警鐘,因為集合是在方法內部直接實例化的。事實上,不必對在工廠之外偷偷摸摸的新運算符感到恐慌,至少在本例中是這樣,因為集合實際上是一種通用的結構,屬於“可創建”類別,而不是“可注入”類別。無論如何,如果您通過在映射器的構造函數中註入集合感到稍微不那麼內疚,請隨意這樣做。有了評論映射器之後,是時候經歷真正的頓悟時刻並構建與前面集合進行調解的代理類了:

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

interface CommentInterface
{
     public function setId($id);
     public function getId();

     public function setContent($content);
     public function getContent();

     public function setPoster($poster);
     public function getPoster();
}</code>

正如您可能預期的那樣,CommentCollectionProxy實現了與真實評論集合相同的接口。但是,它的getComments()方法在幕後完成了實際工作,並通過構造函數中傳遞的映射器從數據庫中延遲加載評論。這個簡單而有效的方法允許您對評論進行各種巧妙的操作,而不會過度費力。您想看看哪些方法嗎?假設您需要從數據庫中獲取與特定博客文章綁定的所有評論。以下代碼片段可以完成這項工作:

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

class Comment implements CommentInterface
{
    protected $id;
    protected $content;
    protected $poster;

    public function __construct($content, $poster) {
        $this->setContent($content);
        $this->setPoster($poster);
    }

    // ... (Comment class methods remain largely unchanged) ...
}</code>

這種方法的缺點是評論首先從存儲中提取,然後注入到帖子對象的內部。如何反過來做,但這次是用代理“欺騙”客戶端代碼?

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

interface CommentCollectionInterface extends Countable, IteratorAggregate
{
    public function getComments();
}</code>

評論不僅在代理被放入foreach循環後從數據庫中透明地延遲加載,而且向客戶端代碼公開的API在整個過程中都保持其原始結構不變。我們甚至敢要求更好的東西嗎?除非您非常貪婪,否則我很難這麼認為。無論哪種情況,此時您都應該了解虛擬代理的幕後實際情況,以及如何在提高領域對象與底層持久層之間交換的操作效率方面充分利用其功能。

結束語

雖然很簡單,特別是如果您大膽地在生產環境中使用它,但前面的示例簡要地展示了一些有趣的概念。首先,虛擬代理不僅易於設置和使用,而且在運行時混合不同的實現以延遲執行代價高昂的任務(例如,延遲加載存儲層中的大量數據或創建重量級對像圖)方面無與倫比。其次,它們是多態性如何成為減少許多面向對象應用程序遭受的常見剛性和脆弱性問題的有效疫苗的經典示例。此外,由於PHP的對像模型很簡單並且支持閉包,因此可以巧妙地混合這些特性並構建其底層邏輯由閉包的優點驅動的代理。如果您想自己應對這一挑戰,那麼我提前祝您一切順利。

(圖片來自imredesiuk / Shutterstock)

以上是虛擬代理的介紹,第2部分的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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