抽象類

抽象類是無法例項化的類。抽象類可以定義抽象方法,這些方法是沒有任何主體的方法,只有一個定義:

abstract class MyAbstractClass {
    abstract public function doSomething($a, $b);
}

抽象類應該由子類擴充套件,然後可以提供這些抽象方法的實現。

像這樣的類的主要目的是提供一種模板,允許子類繼承,強制結構遵守。讓我們舉一個例子詳細說明:

在這個例子中,我們將實現一個 Worker 介面。首先我們定義介面:

interface Worker {
    public function run();
}

為了簡化進一步的 Worker 實現的開發,我們將建立一個已經從介面提供 run() 方法的抽象工作類,但是指定了一些需要由任何子類填充的抽象方法:

abstract class AbstractWorker implements Worker {
    protected $pdo;
    protected $logger;

    public function __construct(PDO $pdo, Logger $logger) {
        $this->pdo = $pdo;
        $this->logger = $logger;
    }

    public function run() {
        try {
            $this->setMemoryLimit($this->getMemoryLimit());
            $this->logger->log("Preparing main");
            $this->prepareMain();
            $this->logger->log("Executing main");
            $this->main();
        } catch (Throwable $e) {
            // Catch and rethrow all errors so they can be logged by the worker
            $this->logger->log("Worker failed with exception: {$e->getMessage()}");
            throw $e;
        }
    }

    private function setMemoryLimit($memoryLimit) {
        ini_set('memory_limit', $memoryLimit);
        $this->logger->log("Set memory limit to $memoryLimit");
    }

    abstract protected function getMemoryLimit();

    abstract protected function prepareMain();

    abstract protected function main();
}

首先,我們提供了一個抽象方法 getMemoryLimit()。從 AbstractWorker 擴充套件的任何類都需要提供此方法並返回其記憶體限制。然後 AbstractWorker 設定記憶體限制並記錄它。

其次,AbstractWorker 在記錄它們被呼叫之後呼叫 prepareMain()main() 方法。

最後,所有這些方法呼叫都被分組在 try-catch 塊中。因此,如果子類定義的任何抽象方法丟擲異常,我們將捕獲該異常,記錄並重新丟擲它。這可以防止所有子類自己實現它。

現在讓我們定義一個從 AbstractWorker 擴充套件的子類:

class TranscactionProcessorWorker extends AbstractWorker {
    private $transactions;

    protected function getMemoryLimit() {
        return "512M";
    }

    protected function prepareMain() {
        $stmt = $this->pdo->query("SELECT * FROM transactions WHERE processed = 0 LIMIT 500");
        $stmt->execute();
        $this->transactions = $stmt->fetchAll();
    }

    protected function main() {
        foreach ($this->transactions as $transaction) {
            // Could throw some PDO or MYSQL exception, but that is handled by the AbstractWorker
            $stmt = $this->pdo->query("UPDATE transactions SET processed = 1 WHERE id = {$transaction['id']} LIMIT 1");
            $stmt->execute();
        }
    }
}

正如你所看到的,TransactionProcessorWorker 很容易實現,因為我們只需要指定記憶體限制並擔心它需要執行的實際操作。TransactionProcessorWorker 中不需要處理錯誤,因為這是在 AbsractWorker 中處理的。

重要的提示

從抽象類繼承時,父類的宣告中標記為 abstract 的所有方法都必須由子類定義(或者子類本身也必須標記為 abstract); 此外,必須使用相同(或限制較少)的可見性來定義這些方法。例如,如果將抽象方法定義為 protected,則必須將函式實現定義為 protected 或 public,而不是 private。

摘自 PHP 文件中的類抽象

如果你沒有在子類中定義父抽象類方法,則會丟擲致命的 PHP 錯誤,如下所示。

致命錯誤: 類 X 包含 1 個抽象方法,因此必須宣告為 abstract 或實現其餘方法(X::x)