面向对象编程(OOP)不仅关乎单个类的外观和操作方式,还涉及一个或多个类的实例如何协同工作。这就是为什么你会看到很多基于“汽车”和“人”的例子,因为它们确实很好地阐述了这个原则。
在我看来,OOP中最重要的课程是封装(encapsulation)和多态(polymorphism)。
封装: 将数据和对该数据进行操作的逻辑紧密地耦合在一起,以简练、逻辑的方式。
多态: 一个对象能够像另一个对象一样看和/或行为。
一个很好的现实世界的例子就是目录迭代器(directory iterator)。这个目录在哪里?也许它是一个本地文件夹,也可能是远程的,比如FTP服务器。谁知道呢!
以下是一个演示封装的基本类树:
<?php
interface DirectoryIteratorInterface
{
public function listDirs();
}
abstract class AbstractDirectoryIterator implements DirectoryIteratorInterface
{
protected $root;
public function __construct($root)
{
$this->root = $root;
}
}
class LocalDirectoryIterator extends AbstractDirectoryIterator
{
public function listDirs()
{
}
}
class FtpDirectoryIterator extends AbstractDirectoryIterator
{
public function listDirs()
{
}
}
每个类/对象都负责其自己的目录列表检索方法。数据(变量)与使用数据的逻辑(类函数即方法)耦合在一起。
但故事还没有结束 - 记得我说过OOP是关于类实例如何协同工作,而不是单个类或对象吗?
好的,那么让我们对这些数据做些什么 - 将它们打印到屏幕上?可以。但是怎么做呢? HTML? 纯文本? RSS? 让我们来解决这个问题。
<?php
interface DirectoryRendererInterface
{
public function render();
}
abstract class AbstractDirectoryRenderer implements DirectoryRendererInterface
{
protected $iterator;
public function __construct(DirectoryIteratorInterface $iterator)
{
$this->iterator = $iterator;
}
public function render()
{
$dirs = $this->iterator->listDirs();
foreach ($dirs as $dir) {
$this->renderDirectory($dir);
}
}
abstract protected function renderDirectory($directory);
}
class PlainTextDirectoryRenderer extends AbstractDirectoryRenderer
{
protected function renderDirectory($directory)
{
echo $directory, "\n";
}
}
class HtmlDirectoryRenderer extends AbstractDirectoryRenderer
{
protected function renderDirectory($directory)
{
echo $directory, "<br>";
}
}
好的,现在我们有了几个用于遍历和呈现目录列表的类树。 那么我们该如何使用它们呢?
$data = new HtmlDirectoryRenderer(
new FtpDirectoryIterator('ftp://example.com/path')
);
$data->render();
$data = new PlainTextDirectoryRenderer(
new LocalDirectoryIterator('/home/pbailey')
);
$data->render();
现在,我知道你在想什么:“但Peter,我不需要这些大的类树来做这个!” 但是如果你这样认为,那么你就错了,就像我怀疑你对“Car”和“People”示例一样。 不要抓住例子中的细节 - 相反,试着理解正在发生的事情。
我们创建了两个类树,其中一个(*DirectoryRenderer
)以预期方式使用另一个(*DirectoryIterator
),这通常称为一个合同。 *DirectoryRenderer
的实例不关心它接收到哪种类型的*DirectoryIterator
实例,*DirectoryIterator
的实例也不关心它们如何被渲染。
为什么? 因为我们已经设计好了它们。 它们只需插入彼此即可正常工作。 这就是面向对象编程(OOP)的实际应用。