作者:大橘为重
链接:https://www.zhihu.com/question/573793228/answer/3341908141
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
访问者模式Visitor Pattern
背景
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许定义一系列操作,而这些操作可以应用于对象结构中的元素,同时又不改变元素类。
结构组成
- 抽象访问者(Visitor): 定义了对每个具体元素类所提供的访问操作接口。
- 具体访问者(ConcreteVisitor): 实现了抽象访问者定义的接口,提供了具体的访问操作。
- 抽象元素(Element): 声明了接受访问者操作的接口。
- 具体元素(ConcreteElement): 实现了抽象元素声明的接口,提供了接受访问者操作的具体实现。
- 对象结构(Object Structure): 包含了具体元素的容器,可以是一个集合或一个复杂的结构,用于存放具体元素。
- 客户端(Client): 通过访问者模式访问对象结构中的元素。
使用场景
访问者模式在以下场景中可能会发挥作用,并在实际项目中找到应用:
编译器设计:在编译器中,访问者模式可以用于遍历抽象语法树(AST)并执行不同的操作,比如语法检查、语义分析、代码生成等。
文档解析和处理:当需要对复杂的文档结构进行解析和处理时,访问者模式可以用于遍历文档元素并执行不同的操作,比如格式转换、信息提取等。
图形化界面库:在图形化界面库中,访问者模式可以用于处理图形元素的遍历和操作,比如在 GUI 中实现布局、事件处理等功能。
网络协议解析:在网络编程中,访问者模式可以用于解析和处理复杂的协议数据包,执行不同的操作,如数据包解析、错误检测等。
游戏开发:在游戏开发中,访问者模式可以用于遍历游戏对象树,并执行不同的操作,如碰撞检测、渲染等。
当一个对象结构包含很多类对象,而且它们之间的类对象比较稳定,但经常需要在这些对象上定义新的操作时,可以使用访问者模式。
当需要对一个对象结构中的对象进行很多不同并且不相关的操作时,访问者模式可以将这些操作分离到不同的访问者中,使得每个访问者只需要关注自己负责的一组操作,从而提高代码的可维护性。
C++示例
这里考虑一个简化的文档结构,包括段落(Paragraph
)和图片(Image
)两种元素。我们希望能够对这些元素进行格式转换、信息提取等操作,而不修改元素类的结构。
DocumentVisitor
定义了两个访问操作,分别用于访问段落Paragraph和图片Image。FormatConverter
是一个具体的访问者,实现了格式转换的具体操作。InformationExtractor
则是另一个具体的访问者,实现了信息提取的具体操作。SayHello 则是另一个具体的访问者,简单的演示一下。
doucumentVisitor.h抽象访问者
#ifndef _DOCUMENTVISITOR_H
#define _DOCUMENTVISITOR_H
#include <iostream>
// Forward declarations
class Paragraph;
class Image;
// 抽象访问者
class DocumentVisitor {
public:
virtual void visit(Paragraph* paragraph) = 0;
virtual void visit(Image* image) = 0;
};
#endif
doucumentElement.h抽象元素
#ifndef _DOCUMENTELEMENT_H
#define _DOCUMENTELEMENT_H
#include <iostream>
#include "doucumentVisitor.h"
// 抽象元素
class DocumentElement {
public:
virtual void accept(DocumentVisitor* visitor) = 0;
};
#endif
paragraph.h具体元素 - 段落
#ifndef _PROGRAPH_H
#define _PROGRAPH_H
#include <iostream>
#include "doucumentElement.h"
// 具体元素 - 段落
class Paragraph : public DocumentElement {
public:
void accept(DocumentVisitor* visitor) override {
visitor->visit(this);
}
};
#endif
image.h具体元素 - 图片
#ifndef _IMAGE_H
#define _IMAGE_H
#include <iostream>
#include "doucumentElement.h"
// 具体元素 - 图片
class Image : public DocumentElement {
public:
void accept(DocumentVisitor* visitor) override {
visitor->visit(this);
}
};
#endif
formatConverter.h具体访问者 - 格式转换器
#ifndef _FORMATCONVERTER_H
#define _FORMATCONVERTER_H
#include <iostream>
#include "doucumentVisitor.h"
// 具体访问者 - 格式转换器
class FormatConverter : public DocumentVisitor {
public:
void visit(Paragraph* paragraph) override {
std::cout << "Converting Paragraph to plain text format." << std::endl;
// 具体的格式转换实现
std::cout << "Paragraph段落格式转换完成" << std::endl;
}
void visit(Image* image) override {
std::cout << "Converting Image to PNG format." << std::endl;
// 具体的格式转换实现
std::cout << "Image图片格式转换完成" << std::endl;
}
};
#endif
informationExtractor.h具体访问者 - 信息提取器
#ifndef _INFORMATIONEXTRACTOR_H
#define _INFORMATIONEXTRACTOR_H
#include <iostream>
#include "doucumentVisitor.h"
// 具体访问者 - 信息提取器
class InformationExtractor : public DocumentVisitor {
public:
void visit(Paragraph* paragraph) override {
std::cout << "Extracting text information from Paragraph." << std::endl;
// 具体的信息提取实现
std::cout << "Paragraph段落信息提取完成" << std::endl;
}
void visit(Image* image) override {
std::cout << "Extracting metadata from Image." << std::endl;
// 具体的信息提取实现
std::cout << "Image图片信息提取完成" << std::endl;
}
};
#endif
sayHello.h具体访问者 - SayHello
#ifndef _SAYHELLO_H
#define _SAYHELLO_H
#include <iostream>
#include "doucumentVisitor.h"
// 具体访问者 - SayHello
class SayHello : public DocumentVisitor {
public:
void visit(Paragraph* paragraph) override {
std::cout << "HELLO, Paragraph" << std::endl;
}
void visit(Image* image) override {
std::cout << "HELLO, Image" << std::endl;
}
};
#endif
main.cpp客户端
#include <iostream>
#include <vector>
#include "image.h"
#include "paragraph.h"
#include "formatConverter.h"
#include "informationExtractor.h"
#include "sayHello.h"
int main() {
// 创建元素:文档和图片
std::vector<DocumentElement*> documentElements = {new Paragraph(), new Image()};
// 创建访问者:格式转换和信息提取
FormatConverter formatConverter;
InformationExtractor informationExtractor;
SayHello sayHello;
// SAY HELLO
std::cout << "SAY HELLO" << std::endl;
for (auto element : documentElements) {
element->accept(&sayHello);
}
std::cout << "===============\n\n";
// 格式转换操作
std::cout << "访问格式转换器" << std::endl;
for (auto element : documentElements) {
element->accept(&formatConverter);
}
std::cout << "===============\n\n";
// 信息提取操作
std::cout << "访问信息提取器" << std::endl;
for (auto element : documentElements) {
element->accept(&informationExtractor);
}
// 释放内存
for (auto element : documentElements) {
delete element;
}
return 0;
}
为这个项目添加CMakeLists.txt进行编译
cmake_minimum_required(VERSION 2.8)
project(visitor)
# 设置C++11编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# Define the source files directory
message(${CMAKE_SOURCE_DIR})
message("=========================")
# Define the include directories
include_directories(
${CMAKE_SOURCE_DIR}
)
# Define the source files
file(GLOB SOURCE_FILES
${CMAKE_SOURCE_DIR}/*.cpp
)
# 添加可执行程序
add_executable(visitor ${SOURCE_FILES})
编译指令如下,依次输入并回车即可
mkdir build
cd build
cmake ..
make
./visitor
运行结果如下
运行结果
总结
通过使用访问者模式,我们可以轻松地添加新的元素和新的访问者,而不需要修改元素类的结构。这样,我们可以在不同的上下文中执行不同的操作,保持代码的可扩展性和灵活性。
访问者模式适用于对象结构相对稳定但需要在其上定义新操作的情况。在这些场景中,访问者模式可以帮助保持代码的灵活性和可扩展性。然而,过度使用访问者模式可能会导致代码变得复杂,因此需要谨慎使用。