首頁 > 後端開發 > php教程 > PHP主|零對像模式 - 域模型中的多態性

PHP主|零對像模式 - 域模型中的多態性

Christopher Nolan
發布: 2025-02-25 14:53:08
原創
443 人瀏覽過

PHP Master | The Null Object Pattern - Polymorphism in Domain Models

核心要點

  • 空對像模式是一種設計模式,利用多態性減少條件代碼,使代碼更簡潔易維護。它提供一個非功能性對象,可以替代真實對象,從而無需進行空值檢查。
  • 空對像模式可以與其他設計模式結合使用,例如工廠模式創建和返回空對象,或策略模式在運行時更改對象的行為。
  • 空對像模式的潛在缺點是可能導致創建不必要的對象,增加內存使用。它也可能使代碼更複雜,因為需要創建額外的類和接口。
  • 實現空對像模式需要創建一個空對像類,該類實現與真實對象相同的接口。這個空對象為接口中的所有方法提供默認實現,允許它替代真實對象。這使得代碼更健壯,更不容易出錯。

雖然並非完全符合規範,但可以說正交性是基於“良好設計”原則的軟件系統的精髓,其中各個模塊彼此解耦,使系統不易出現僵化和脆弱的問題。當然,談論正交系統的優點比實際運行生產系統更容易;這個過程通常是追求烏托邦。即使如此,在系統中實現高度解耦的組件絕非烏托邦的概念。多態性等多種編程概念允許設計靈活的程序,其各個部分可以在運行時切換,其依賴關係可以以抽象的形式表達,而不是具體的實現。我想說,無論我們是在實現基礎設施還是應用程序邏輯,“面向接口編程”的舊格言隨著時間的推移已經得到了普遍的採用。然而,當踏上領域模型的領域時,情況就大相徑庭了。坦率地說,這是一個可預測的場景。畢竟,為什麼一個相互關聯的對象網絡(其數據和行為受限於明確定義的業務規則)應該是多態的呢?它本身並沒有多大意義。不過,此規則有一些例外,最終可能適用於這種情況。第一個例外是使用虛擬代理,它有效地與實際領域對象實現的接口相同。另一個例外是所謂的“空情況”,這是一種特殊情況,其中操作最終可能分配或返回空值,而不是填充良好的實體。在傳統的非多態方法中,模型的使用者必須檢查這些“有害的”空值並優雅地處理該條件,從而在整個代碼中產生條件語句的爆炸。幸運的是,只需創建領域對象的多分支實現即可輕鬆解決這種看似混亂的情況,該實現將實現與所討論對象相同的接口,只是其方法不會執行任何操作,因此將客戶端代碼從在執行操作時重複檢查難看的空值中解脫出來。毫不奇怪,這種方法是一種稱為空對象的設計模式,它將多態性的優勢發揮到極致。在本文中,我將演示該模式在幾種情況下的好處,並向您展示它們如何緊密地依附於多態方法。

處理非多態條件

正如人們所料,在展示空對像模式的優點時,有幾種方法可以嘗試。我發現一種特別直接的方法是實現一個數據映射器,它最終可能從通用查找器返回空值。假設我們已經成功創建了一個骨架領域模型,該模型僅由一個用戶實體組成。接口及其類如下所示:

<?php namespace Model;

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

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}
登入後複製
登入後複製
<?php namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }
}
登入後複製
登入後複製

User 類是一個反應式結構,它實現了一些 mutators/accessors 來定義一些用戶的數據和行為。有了這個構造的領域類,我們現在可以更進一步,定義一個基本的數據映射器,它將我們的領域模型和數據訪問層彼此隔離。

<?php namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;

    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }

    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }

    private function createUser(array $row) {
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user;
    }
}
登入後複製

首先應該出現的是映射器的 fetchById() 方法是塊中的淘氣鬼,因為它在數據庫中沒有用戶與給定的 ID 匹配時有效地返回 null。出於顯而易見的原因,這個笨拙的條件使得客戶端代碼每次調用映射器的查找器時都必須費力地檢查空值。

<?php use LibraryLoaderAutoloader,
    LibraryDatabasePdoAdapter,
    ModelMapperUserMapper;

require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=test", "myusername", "mypassword");

$userMapper = new UserMapper($adapter);

$user = $userMapper->fetchById(1);

if ($user !== null) {
    echo $user->getName() . " " . $user->getEmail();
}
登入後複製

乍一看,這不會有什麼問題,只要在一個地方進行檢查即可。但是,如果相同的行出現在多個頁面控制器或服務層中,您會不會撞到磚牆上?在你意識到之前,映射器返回的看似無辜的 null 會產生大量重複的條件,這是設計不良的凶兆。

從客戶端代碼中刪除條件語句

然而,無需焦慮,因為這正是空對像模式顯示多態性為何是天賜之物的情況。如果我們想一勞永逸地擺脫那些討厭的條件語句,我們可以實現前面 User 類的多態版本。

<?php namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() { }

    public function setName($name) { }
    public function getName() { }

    public function setEmail($email) { }
    public function getEmail() { }
}
登入後複製

如果您期望一個完整的實體類打包各種各樣的裝飾,那麼恐怕您會非常失望。 “null”版本的實體符合相應的接口,但方法是空包裝器,沒有實際的實現。雖然 NullUser 類的存在顯然沒有給我們帶來任何值得稱讚的有用東西,但它是一個簡潔的結構,允許我們將所有之前的條件語句扔進垃圾桶。想看看它是如何實現的嗎?首先,我們應該做一些前期工作並重構數據映射器,以便它的查找器返回一個空用戶對象而不是空值。

<?php namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser,
    ModelNullUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;

    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }

    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        return $this->createUser($this->adapter->fetch());
    }

    private function createUser($row) {
        if (!$row) {
            return new NullUser;
        }
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user; 
    }
}
登入後複製

映射器的 createUser() 方法隱藏了一個微小的條件,因為它現在負責在傳遞給查找器的 ID 沒有返回有效用戶時創建一個空用戶。即便如此,這種細微的代價不僅可以節省客戶端代碼進行大量重複檢查的工作,而且還可以將其變成一個寬鬆的使用者,當它必須處理空用戶時不會抱怨。

<?php namespace Model;

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

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}
登入後複製
登入後複製

這種多態方法的主要缺點是,任何使用它的應用程序都將變得過於寬鬆,因為它在處理無效實體時永遠不會崩潰。在最壞的情況下,用戶界面只會顯示一些空白行,但沒有任何真正嘈雜的東西讓我們感到厭惡。當掃描早期 NullUser 類的當前實現時,這一點尤其明顯。即使是可行的,更不用說推薦的了,在保持其多態性不變的同時,也可以在空對像中封裝邏輯。我什至可以說,空對象非常適合封裝默認數據和行為,這些數據和行為應該只在少數特殊情況下向客戶端代碼公開。如果您足夠雄心勃勃並且想使用簡單的空用戶對象嘗試這個概念,那麼當前的 NullUser 類可以按以下方式重構:

<?php namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }
}
登入後複製
登入後複製

增強的 NullUser 版本比其安靜的前身略微更具表達性,因為它的 getter 提供了一些基本的實現,以便在請求無效用戶時返回一些默認消息。雖然微不足道,但這項更改對客戶端代碼處理空用戶的方式產生了積極的影響,因為這次使用者至少清楚地知道當他們試圖從存儲中提取不存在的用戶時出現了一些問題。這是一個不錯的突破,它不僅展示瞭如何實現實際上根本不是空的空對象,還展示了根據特定需求在相關對象內部移動邏輯是多麼容易。

結束語

有些人可能會說,實現空對像很麻煩,尤其是在 PHP 中,OOP 的核心概念(如多態性)被明顯低估了。他們在某種程度上是對的。儘管如此,逐步採用值得信賴的編程原則和設計模式,以及該語言對像模型目前達到的成熟度水平,為穩步前進和開始使用一些不久前被認為是複雜、不切實際的概念的“奢侈品”提供了所有必要的基礎。空對像模式屬於此類別,但其實現非常簡單和優雅,以至於在清除客戶端代碼中的重複空值檢查時很難不覺得它很吸引人。 圖片來自 Fotolia

(由於篇幅限制,此處省略了原文中的FAQ部分。)

以上是PHP主|零對像模式 - 域模型中的多態性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板