php设计模式(二十四):访问者模式(Visitor)
- 陈大剩
- 2023-06-15 10:41:06
- 1593
访问者模式
访问者模式又称为:Visitor。访问者模式是一种行为设计模式,它能将算法与其所作用的对象隔离开来。
访问者模式表示一个作用于某对象结构中各元素的操作。它可以在不修改各元素类的前提下定义作用于这些元素的新操作,即动态的增加具体访问者角色。
也就是说访问者模式是适用于 稳定的结构,什么是稳定的结构呢? 稳定结构是指我们的对象在早之前已经开发好了,功能都实现好了(前面版本)。访问者模式是说现在我们突然要给这个开发好的结构(稳定结构),在不影响原来稳定的基础上,增添一些职责或者功能,所以说访问者模式是马后炮行为。
访问者模式是利用了双重分派。先将访问者传入元素对象的 Accept
方法中,然后元素对象再将自己传入访问者,之后访问者执行元素的相应方法。
问题
假设我们已经编写好一个操作(打开、关闭、或者内容) Pdf、Word、Xml 文件代码,现在突然我们顶头上司,需要我们实现将这些文件里的文字内容导出到 Txt 文件中。
我们可能会对每个文件对象中都写一个将内容导出 Txt 文件的方法。
过了几天顶头上司又要我们写一个将这些文件内容导出成 图片 可视化格式(类似截图内容),我们又在每个文件对象中都写一个将内容导出 图片 文件的方法。
问题是我们后面可能顶头上司会让我们支持导出其他更多的格式,我们如果这么一个个写的对象类中,会使代码越来越复杂且臃肿,且不说我们新增导出各种格式不会影响这个类原有功能的使用,可能到后面代码多起来我们自己都看不懂?所以我们应该怎么解决呢?
解决方法
我们可以使用访问器模式,给 Pdf、Word、Xml 文件代码提供一个访问者,以用来访问它们。
顾名思义访问者就是拜访的形式去拜访 Pdf、Word、Xml 类,所以我们 Pdf、Word、Xml 类中需要添加一个 接受访问者的方法 Accept
(接受访问者的方法),然后再将当前类的传递给访问者(访问者需要有获取所有数据的权限)。
我们就做到了只要在 Pdf、Word、Xml 类代码中添加一个接受方法,再将类对象传递给访问者。
具体的导出工作交个访问者具体方法去做 ,就不会导致 Pdf、Word、Xml 类代码复杂且难以维护。
可能往下看代码容易理解点,那下面就看代码吧。
结构
FileInterface:文件对象接口类。
PdfFile、WordFile、XmlFile:具体的文件类,实现文件接口类,提供访问本身内容的方法。
VisitorInterface:访问者接口类,提供操作接口方法。
FileToTxt:访问者实现类,提供具体向文件的操作方法。
代码示例
文件接口类
interface FileInterface
{
/**
* 接受者(接受访问者)
* @return mixed
* @author chendashengpc
*/
public function accept(VisitorInterface $visitor);
}
文件具体实现类
// pdf
class PdfFile implements FileInterface
{
public function accept(VisitorInterface $visitor)
{
return $visitor->PdfToTxt($this);
}
/**
* 打开 PDF 文件内容
* @return string
* @author chendashengpc
*/
public function openPdf(): string
{
return '我是 Pdf 文件内容';
}
}
// word
class WordFile implements FileInterface
{
public function accept(VisitorInterface $visitor)
{
return $visitor->WordToTxt($this);
}
/**
* 打开 Word 文件内容
* @return string
* @author chendashengpc
*/
public function openWord(): string
{
return '我是 Word 文件内容';
}
}
// xml
class XmlFile implements FileInterface
{
public function accept(VisitorInterface $visitor)
{
return $visitor->XmlToTxt($this);
}
/**
* 打开 Xml 文件内容
* @return string
* @author chendashengpc
*/
public function openXml(): string
{
return '我是 Xml 文件内容';
}
}
访问者接口类
interface VisitorInterface
{
/**
* pdf 转 txt
* @param FileInterface $file
* @return mixed
* @author chendashengpc
*/
public function PdfToTxt(FileInterface $file);
/**
* Word 转 txt
* @param FileInterface $file
* @return mixed
* @author chendashengpc
*/
public function WordToTxt(FileInterface $file);
/**
* Xml 转 txt
* @param FileInterface $file
* @return mixed
* @author chendashengpc
*/
public function XmlToTxt(FileInterface $file);
}
访问者具体实现类
/**
* 文件转 txt
*/
class FileToTxt implements VisitorInterface
{
/**
* pdf 转 txt
* @param FileInterface $file
* @return mixed
* @author chendashengpc
*/
public function PdfToTxt(FileInterface $file)
{
return $file->openPdf();
}
/**
* Word 转 txt
* @param FileInterface $file
* @return mixed
* @author chendashengpc
*/
public function WordToTxt(FileInterface $file)
{
return $file->openWord();
}
/**
* Xml 转 txt
* @param FileInterface $file
* @return mixed
* @author chendashengpc
*/
public function XmlToTxt(FileInterface $file)
{
return $file->openXml();
}
}
客户端使用
$fileToTxt = new FileToTxt();
$pdf = new PdfFile();
echo $pdf->accept($fileToTxt) . PHP_EOL;
$word = new WordFile();
echo $word->accept($fileToTxt) . PHP_EOL;
$xml = new XmlFile();
echo $xml->accept($fileToTxt) . PHP_EOL;
输出
我是 Pdf 文件内容
我是 Word 文件内容
我是 Xml 文件内容
UML
优缺点
优点
- 开闭原则。 可以引入在不同类对象上执行的新行为,且无需对这些类做出修改。
- 单一职责原则。可将同一行为的不同版本移到同一个类中。
- 访问者对象可以在与各种对象交互时收集一些有用的信息。当你想要遍历一些复杂的对象结构(例如对象树),并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。
缺点
- 每次在元素层次结构中添加或移除一个类时,都要更新所有的访问者。
- 在访问者同某个元素进行交互时,它们可能没有访问元素私有成员变量和方法的必要权限。