抽象类

抽象类是无法实例化的类。抽象类可以定义抽象方法,这些方法是没有任何主体的方法,只有一个定义:

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)