写在前面
Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!
需求背景
通过构建一个文档处理系统,来深入学习访问者模式在实际生活中的应用,该系统需要处理多种类型的文档元素,如文本段落、图片、表格和代码块等,并对这些元素执行各种操作,如渲染、导出、拼写检查和内容统计等。 首先,使用传统方式实现处理系统,然后分析其中存在的问题,最后使用访问者模式进行重构,体会重构后带来的好处。
传统方式实现
抽象文档元素类
csharp
public abstract class DocumentElement {
private String content;
public DocumentElement(String content) {
this.content = content;
}
/**
* 渲染操作
*/
public abstract void render();
/**
* 导出操作
*/
public abstract String export();
/**
* 拼写检查操作
*/
public abstract void spellCheck();
public String getContent() {
return content;
}
}
DocumentElement
抽象类定义了所有文档元素共有的属性(内容) 和 操作(渲染、导出、拼写检查)。每个子类均需要实现所有抽象方法。
具体元素类
TextElement
typescript
public class TextElement extends DocumentElement {
public TextElement(String content) {
super(content);
}
@Override
public void render() {
System.out.println("渲染文本段落: " + getContent());
}
@Override
public String export() {
return "<p>" + getContent() + "</p>";
}
@Override
public void spellCheck() {
System.out.println("对文本段落进行拼写检查: " + getContent());
}
}
TableElement
csharp
public class TableElement extends DocumentElement {
private int rows;
private int columns;
public TableElement(String content, int rows, int columns) {
super(content);
this.rows = rows;
this.columns = columns;
}
@Override
public void render() {
System.out.println("渲染表格: " + getRows() + "x" + getColumns() + ", 标题: " + getContent());
}
@Override
public String export() {
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<table><caption>").append(getContent()).append("</caption>");
for (int i = 0; i < rows; i++) {
strBuilder.append("<tr>");
for (int j = 0; j < columns; j++) {
strBuilder.append("<td>数据</td>");
}
strBuilder.append("</tr>");
}
strBuilder.append("</table>");
return strBuilder.toString();
}
@Override
public void spellCheck() {
System.out.println("对表格标题进行拼写检查: " + getContent());
}
public int getColumns() {
return columns;
}
public int getRows() {
return rows;
}
}
ImageElement
typescript
public class ImageElement extends DocumentElement {
private String source;
public ImageElement(String content, String source) {
super(content);
this.source = source;
}
@Override
public void render() {
System.out.println("渲染图片: " + getSource() + ", 描述: " + getContent());
}
@Override
public String export() {
return "<img src = " + getSource() + "alt = " + getContent() + " />";
}
@Override
public void spellCheck() {
System.out.println("对图片描述进行拼写检查: " + getContent());
}
public String getSource() {
return source;
}
}
CodeElement
typescript
public class CodeElement extends DocumentElement {
private String language;
public CodeElement(String content, String language) {
super(content);
this.language = language;
}
@Override
public void render() {
System.out.println("渲染代码块: 语言 = " + getLanguage() + "\n" + getContent());
}
@Override
public String export() {
return "<pre><code class = " + getLanguage() + ">" + getContent() + "</code></pre>";
}
@Override
public void spellCheck() {
System.out.println("代码块不需要拼写检查");
}
public String getLanguage() {
return language;
}
}
Document
csharp
public class Document {
private String title;
private List<DocumentElement> elements;
public Document(String title) {
this.title = title;
this.elements = new ArrayList<>();
}
public void addElement(DocumentElement element) {
elements.add(element);
}
public void render() {
System.out.println("渲染文档: " + title);
for (DocumentElement element : elements) {
element.render();
}
}
public String export() {
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<html><head><title>").append(title).append("</title></head><body>");
for (DocumentElement element : elements) {
strBuilder.append(element.export());
}
strBuilder.append("</body></html>");
return strBuilder.toString();
}
public void spellCheck() {
System.out.println("对文档进行拼写检查: " + title);
for (DocumentElement element : elements) {
element.spellCheck();
}
}
}
Document
包含一个元素列表,并提供了渲染、导出和拼写检查整个文档的方法
测试类
typescript
public class VisitorTest {
@Test
public void test_document() {
Document document = new Document("Java编程指南");
document.addElement(new TextElement("Java是一种广泛使用的编程语言。"));
document.addElement(new ImageElement("Java Logo", "java_logo.png"));
document.addElement(new TableElement("Java版本历史", 5, 3));
document.addElement(new CodeElement("public class Hello{public static void main(String[] args){System.out.println("Hello, World!");}}", "Java"));
System.out.println("======== 渲染文档 ========");
document.render();
System.out.println("\n ======== 导出文档 ========");
String exportDoc = document.export();
System.out.println(exportDoc);
System.out.println("\n ======== 拼写检查 ========");
document.spellCheck();
}
}
执行结果
typescript
======== 渲染文档 ========
渲染文档: Java编程指南
渲染文本段落: Java是一种广泛使用的编程语言。
渲染图片: java_logo.png, 描述: Java Logo
渲染表格: 5x3, 标题: Java版本历史
渲染代码块: 语言 = Java
public class Hello{public static void main(String[] args){System.out.println("Hello, World!");}}
======== 导出文档 ========
<html><head><title>Java编程指南</title></head><body><p>Java是一种广泛使用的编程语言。</p><img src = java_logo.pngalt = Java Logo /><table><caption>Java版本历史</caption><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr></table><pre><code class = Java>public class Hello{public static void main(String[] args){System.out.println("Hello, World!");}}</code></pre></body></html>
======== 拼写检查 ========
对文档进行拼写检查: Java编程指南
对文本段落进行拼写检查: Java是一种广泛使用的编程语言。
对图片描述进行拼写检查: Java Logo
对表格标题进行拼写检查: Java版本历史
代码块不需要拼写检查
Process finished with exit code 0
传统实现中存在的问题
添加新操作的困难
现在系统内需要增加一个内容统计功能,用于统计文档中各元素的字符数、单词数等信息,传统实现中,要修改大量代码。 首先 抽象类 DocumentElement
中添加一个新的抽象方法:
csharp
/* ============================== 增加统计文档内容功能 ============================== */
public abstract void countStats();
然后,我们需要在每个具体元素类中实现这个抽象方法:
java
// 在 CodeElement 中添加
@Override
public void countStats() {
String code = getContent();
int charCount = code.length();
int lineCount = code.split("\n").length;
System.out.println("代码块统计: " + charCount + " 个字符, " + lineCount + " 行代码");
}
// 在 ImageElement 中添加
@Override
public void countStats() {
String description = getContent();
int charCount = description.length();
int wordCount = description.split("\s+").length;
System.out.println("图片描述统计: " + charCount + " 个字符, " + wordCount + " 个单词");
}
@Override
public void countStats() {
String caption = getContent();
int charCount = caption.length();
int wordCount = caption.split("\s+").length;
int cellCount = getRows() * getColumns();
System.out.println("表格统计: 标题包含 " + charCount + " 个字符, " + wordCount + " 个单词, 共 " + cellCount + " 个单元格");
}
// 在 TextElement 中添加
@Override
public void countStats() {
String content = getContent();
int charCount = content.length();
int wordCount = content.split("\s+").length;
System.out.println("文本段落统计: " + charCount + " 个字符, " + wordCount + " 个单词");
}
最后,我们还需要在 Document
类中添加一个方法来调用所有元素的统计方法:
csharp
public void countStats() {
System.out.println("统计文档内容: " + title);
for (DocumentElement element : elements) {
element.countStats();
}
}
在修改过程中,不难发现:每次添加新操作,都需要修改所有的元素类,而在大型系统中,可能有几十甚至上百种元素类型,不仅工作量巨大,而且容易遗漏某些类的修改,导致发生错误。
代码分散,维护困难
在新增功能时不难看出,类似的一些功能,分散在每个类中,难以统一维护,当操作逻辑需要修改时,需要在多个文件中进行相同的修改。
违反开闭原则
传统实现中明显违反了开闭原则,每次添加新操作,我们都需要修改现有的元素类,而不是通过添加新的类或模块来实现。
访问者模式
什么是访问者模式
访问者模式是一种行为设计模式,允许在不修改已有类结构的情况下,定义作用于这些类的新操作,这种模式的核心思想是将操作与数据结构分离,使得可以在不改变元素类的前提下,定义新的操作。
通俗的来说,访问者模式就像是一个检查员,在各个场所进行检查。不同类型的检查员可以对同一组场所执行不同类型的检查,而场所本身不需要知道如何进行这些检查,只需要接待检察员即可。
访问者结构
- 访问者(Visitor): 定义对每种元素类型执行的操作,通常包含多个名为
visit
的方法,每个方法接受不同类型的元素做为参数。 - 具体访问者(Concrete Visitor): 实现访问者接口中声明的方法,为每种元素类型提供具体的操作实现。
- 元素(Element): 定义一个接受访问者的方法(通常命名为"accept"),该方法以一个访问者对象作为参数。
- 具体元素(Concrete Element): 实现元素接口,在"accept"方法中调用访问者的对应 "visit" 方法。
- 对象结构(Object structrue): 包含所有元素的集合,提供一个高层接口允许访问者访问其所有元素。
使用访问者模式重构
重构核心思想
重构的核心思想是将操作(如渲染、导出、拼写、检查等) 从元素类中分离出来,定义为独立的访问者类,元素类中只需要提供一个 accept
方法来接受访问者,而不需要知道具体的操作细节。
步骤
1.定义访问者接口
首先,定义一个访问者接口,其包含针对每种元素类型的visit
方法:
csharp
public interface DocumentElementVisitor {
void visit(TextElement element);
void visit(ImageElement element);
void visit(TableElement element);
void visit(CodeElement element);
}
DocumentElementVisitor
定义了访问者可以访问的所有元素类型,每个visit
方法接受一个特定类型的元素作为参数。
2.修改元素接口
接下来,修改元素接口,新增一个 accept
方法来接受访问者:
typescript
public abstract class DocumentElement {
private String content;
public DocumentElement(String content) {
this.content = content;
}
public String getContent() {
return content;
}
/**
* 接受访问者的方法
*
* @param visitor
*/
public abstract void accept(DocumentElementVisitor visitor);
}
- 移除了原来的
render()
、export()
和spellCheck()
方法,这些操作酱油访问者来实现。
3.实现具体元素类
typescript
public class CodeElement extends DocumentElement {
private String language;
public CodeElement(String content, String language) {
super(content);
this.language = language;
}
public String getLanguage() {
return language;
}
@Override
public void accept(DocumentElementVisitor visitor) {
visitor.visit(this);
}
}
public class ImageElement extends DocumentElement {
private String source;
public ImageElement(String content, String source) {
super(content);
this.source = source;
}
public String getSource() {
return source;
}
@Override
public void accept(DocumentElementVisitor visitor) {
visitor.visit(this);
}
}
public class TableElement extends DocumentElement {
private int rows;
private int columns;
public TableElement(String content, int rows, int columns) {
super(content);
this.rows = rows;
this.columns = columns;
}
public int getRows() {
return rows;
}
public int getColumns() {
return columns;
}
@Override
public void accept(DocumentElementVisitor visitor) {
visitor.visit(this);
}
}
- 在元素类的
accept()
方法中,调用访问者的visit()
方法,并将自身作为参数传入,这是实现双重分发的关键步骤。
4.实现具体访问者类
typescript
public class ExportVisitor implements DocumentElementVisitor {
private StringBuilder result = new StringBuilder();
@Override
public void visit(TextElement element) {
result.append("<p>")
.append(element.getContent())
.append("</p>");
}
@Override
public void visit(ImageElement element) {
result.append("<img src=>")
.append(element.getSource())
.append(" alt=")
.append(element.getContent())
.append(" />");
}
@Override
public void visit(TableElement element) {
result.append("<table><caption>")
.append(element.getContent())
.append("</caption>");
for (int i = 0; i < element.getRows(); i++) {
result.append("<tr>");
for (int j = 0; j < element.getColumns(); j++) {
result.append("<td>数据</td>");
}
result.append("</tr>");
}
result.append("/table");
}
@Override
public void visit(CodeElement element) {
result.append("<pre><code class=")
.append(element.getLanguage())
.append(" >")
.append(element.getContent())
.append("</code></pre>");
}
public String getResult() {
return result.toString();
}
}
public class RenderVisitor implements DocumentElementVisitor {
@Override
public void visit(TextElement element) {
System.out.println("渲染文本段落: " + element.getContent());
}
@Override
public void visit(ImageElement element) {
System.out.println("渲染图片: " + element.getSource() + " , 描述: " + element.getContent());
}
@Override
public void visit(TableElement element) {
System.out.println("渲染表格: " + element.getRows() + " x " + element.getColumns() + " ,标题: " + element.getContent());
}
@Override
public void visit(CodeElement element) {
System.out.println("渲染代码块: 语言=" + element.getLanguage() + "\n" + element.getContent());
}
}
public class SpellCheckVisitor implements DocumentElementVisitor {
@Override
public void visit(TextElement element) {
System.out.println("对文本段落进行拼写检查: " + element.getContent());
}
@Override
public void visit(ImageElement element) {
System.out.println("对图片描述进行拼写检查: " + element.getContent());
}
@Override
public void visit(TableElement element) {
System.out.println("对表格标题进行拼写检查: " + element.getContent());
}
@Override
public void visit(CodeElement element) {
System.out.println("代码块不需要拼写检查");
}
}
- 每个访问者类都实现了
DocumentElementVisitor
接口,并为每种元素类型提供了具体的操作实现。
5.修改文档类
typescript
public class Document {
private String title;
private List<DocumentElement> elements;
public Document(String title) {
this.title = title;
this.elements = new ArrayList<>();
}
public void addElement(DocumentElement element) {
elements.add(element);
}
public void accept(DocumentElementVisitor visitor) {
for (DocumentElement element : elements) {
element.accept(visitor);
}
}
public String getTitle() {
return title;
}
}
- 新增一个
accept()
方法,遍历所有元素让它们接受访问者。
6.测试类
typescript
@Test
public void test_doc() {
Document document = new Document("Java 编程指南");
document.addElement(new TextElement("Java是一种广泛使用的编程语言。"));
document.addElement(new ImageElement("Java Logo", "java_logo.png"));
document.addElement(new TableElement("Java版本历史", 5, 3));
document.addElement(new CodeElement("public class Hello{public static void main(String[] args){System.out.println("Hello, World!");}}", "Java"));
System.out.println("======== 渲染文档 ========");
RenderVisitor renderVisitor = new RenderVisitor();
document.accept(renderVisitor);
System.out.println("\n ======== 导出文档 ========");
ExportVisitor exportVisitor = new ExportVisitor();
document.accept(exportVisitor);
String exportedDoc = "<html><head><title>" + document.getTitle() + "</title></head><body>" + exportVisitor.getResult() + "</body></html>";
System.out.println(exportedDoc);
}
执行结果
typescript
======== 渲染文档 ========
渲染文本段落: Java是一种广泛使用的编程语言。
渲染图片: java_logo.png , 描述: Java Logo
渲染表格: 5 x 3 ,标题: Java版本历史
渲染代码块: 语言=Java
public class Hello{public static void main(String[] args){System.out.println("Hello, World!");}}
======== 导出文档 ========
<html><head><title>Java 编程指南</title></head><body><p>Java是一种广泛使用的编程语言。</p><img src=>java_logo.png alt=Java Logo /><table><caption>Java版本历史</caption><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr><tr><td>数据</td><td>数据</td><td>数据</td></tr>/table<pre><code class=Java >public class Hello{public static void main(String[] args){System.out.println("Hello, World!");}}</code></pre></body></html>
Process finished with exit code 0
重构后代码的可扩展性
实现统计功能
ini
public class StatsVisitor implements DocumentElementVisitor {
private int totalCharCount = 0;
private int totalWordCount = 0;
private int totalLinCount = 0;
@Override
public void visit(TextElement element) {
String content = element.getContent();
int charCount = content.length();
int wordCount = content.split("\s+").length;
totalCharCount += charCount;
totalWordCount += wordCount;
System.out.println("文本段落统计: " + charCount + " 个字符, " + wordCount + " 个单词");
}
@Override
public void visit(ImageElement element) {
String description = element.getContent();
int charCount = description.length();
int wordCount = description.split("\s+").length;
totalCharCount += charCount;
totalWordCount += wordCount;
System.out.println("图片描述统计: " + charCount + "个字符, " + wordCount + " 个单词");
}
@Override
public void visit(TableElement element) {
String caption = element.getContent();
int charCount = caption.length();
int wordCount = caption.split("\s+").length;
int cellCount = element.getRows() * element.getColumns();
totalCharCount += charCount;
totalWordCount += wordCount;
System.out.println("表格统计: 标题包含 " + charCount + " 个字符," + wordCount + " 个单词,共 " + cellCount + " 个单元格");
}
@Override
public void visit(CodeElement element) {
String code = element.getContent();
int charCount = code.length();
int lineCount = code.split("\n").length;
totalCharCount += charCount;
totalLinCount += lineCount;
System.out.println("代码块统计: " + charCount + " 个字符, " + lineCount + " 行代码");
}
public void printTotalStats() {
System.out.println("\n总计统计: ");
System.out.println("总字符数: " + totalCharCount);
System.out.println("总单词数: " + totalWordCount);
System.out.println("总代码行数: " + totalLinCount);
}
}
StatsVisitor
新增统计访问者,不需要修改任何现有的类,所有与内容统计相关的代码都集中在一个访问者类中。
添加新元素类型: 公式元素
在传统实现中,我们仅需要创建一个新的元素类FormulaElement
,继承自 DocumentElement
,在这个类中实现所有抽象方法(render()
、export()
、spellCheck()
等)
在访问者模式中,将变得复杂起来,我们需要:
- 创建一个新的元素类
FormulaEment
,继承自DocumentElementVisitor
- 在
DocumentElementVisitor
中添加一个新的visit
方法 - 在所有现有的访问者类中实现这个新方法。 该功能添加将修改多个文件,当元素层次结构经常变化时,访问者模式可能不是最佳选择。
前后对比
- 重构前
- 每个元素都包含所有操作的实现。
- 添加新操作需要修改所有元素类。
- 相关功能的代码分散在多个类中。
- 重构后
- 元素类中只包含一个
accept()
方法,不包含具体操作的实现。 - 每种操作都由一个独立的访问者类实现。
- 添加新操作只需要添加一个新的访问者类,不需要修改元素类。
- 相关功能的代码集中在一个访问者类中。
长话短说
核心思想
- 操作与数据结构分离 访问者模式的核心思想是将操作与胡数据结构分离,在传统的面向对象设计中,我们通常将数据和操作封装在一个类中,而访问者模式采取了不同的方法:
- 数据结构: 由元素类负责,它们只包含数据和基本行为。
- 操作: 由访问者类负责,它们包含针对不同元素类型的操作实现。 这种分离,使得我们可以在不修改元素类的情况下添加新的操作。
- 双重分发机制 访问者模式通过 "双重分发" 的机制来解决方法调用问题,在Java等单分发语言中,方法的调用取决于两个因素即对象的运行时类型和方法的名称,但在某些情况下我们需要根据两个对象的类型来决定执行哪个方法,这就是双重分发问题,通常使用
instanceof
关键字进行类型检查,然后决定调用哪个方法,但访问者模式通过以下步骤实现了双重分发 - 元素调用访问者的
visit
方法,并将自身作为参数传入。 - 由于Java的方法重载机制,会根据参数的静态类型选择合适的
visit
方法。 - 访问者可以根据元素的具体类型执行相应的操作。 这种机制避免了显示的类型检查及类型转换,使得代码更加易于维护。
- 集中相关操作 访问者模式将相关的操作集中在一个访问者类中,而不是分散在多个元素类中,这种集中有以下好处:
- 简化修改,当需要修改某个操作的逻辑时,只需要修改一个访问者类,而不是多个元素类。
- 提高内聚性,相关的功能代码集中在一起,便于理解和维护。
- 便于优化,可以在访问者类中实现全局优化,而不受元素类的限制。
- 状态累积 访问者可以在遍历过程中累积状态,在处理复杂对象结构时非常有用,例如,在文档处理中,统计访问者可以累积各种统计信息,并在遍历结束后提供总计结果。
适用场景
- 对象结构相对稳定,但操作经常变化 当系统中的对象结构相对稳定,但需要经常添加新的操作时,可以使用访问者,在这种情况下,访问者模式允许我们在不修改元素类的情况下添加新的操作。
- 需要对复杂对象结构执行多种不同操作 当需要对一个复杂的对象结构执行多种不同且不相关的操作时,访问者模式可以将这些操作分离到不同的访问者类中。避免使元素类变的膨胀。
- 需要跨越多个不同类的对象进行操作 当操作需要处理多种不同类型的对象,并且每种对象的处理逻辑不同时,访问者模式可以避免使用类型检查和类型转换。
实施步骤
1.定义元素接口
定义元素接口,应该包含一个accept
方法,该方法接受一个访问者对象作为参数
csharp
public interface Element{
void accept(Visitor visitor);
}
2.实现具体元素类
实现具体的元素类,它们应该实现元素接口,并在accept
方法中调用访问者的相应 visit
方法
typescript
public class ConcreteElementA implements Element {
private String data;
public ConcreteElementA(String data){
this.data = data;
}
public String getData(){
return data;
}
@Override
public void accept(Visitor visitor){
visitor.visit(this);
}
}
public class ConcreteElementB implements Element{
private int value;
public ConcreteElementB(int value){
this.value = value;
}
public int getValue(){
return value;
}
@Override
public void accept(Visitor visitor){
visitor.visit(this);
}
}
定义访问者接口
定义访问者接口,接口中为每种元素类型定义一个 visit
方法:
csharp
public interface Visitor{
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
4.实现具体访问者类
实现访问者接口,并为每种元素类型提供具体的操作实现:
typescript
public class ConcreteVisitor1 implements Visitor{
@Override
public void visit(ConcreteElementA element){
System.out.println("Visitor1:Processing" + element.getValue());
}
@Override
public void visit(ConcreteElementB element){
System.out.println("Visitor1:Processing" + element.getValue());
}
}
public class ConcreteVisitor2 implements Visitor{
@Override
public void visit(ConcreteElementA element){
System.out.println("Visitor2:Processing" + element.getValue());
}
@Override
public void visit(ConcreteElementB element){
System.out.println("Visitor2:Processing" + element.getValue());
}
}
5.创建对象结构
可以是一个简单的集合,也可以是一个复杂的组合结构:
typescript
public class ObjectStructure{
private List<Element> elements = new ArrayList<>();
public void addElement(Element element){
elements.add(element);
}
public void removeElement(Element element){
elements.remove(element);
}
public void accept(Visitor visitor){
for(Element element : elements){
element.accept(visitor);
}
}
}
6.客户端代码
java
public class Client{
public static void main(String[] args){
ObjectStructure structrue = new ObjectStructure();
structrue.addElement(new ConcreteElementA("Element A"));
structrue.addElement(new ConcreteElementB(42));
Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
// 使用访问者1处理对象结构
System.out.println("Using Visitor1: ");
structrue.accept(visitor1);
// 使用访问者2处理对象结构
System.out.println("Using Visitor2: ");
structrue.accept(visitor2);
}
}
访问者模式的最佳实践
1.保持元素接口简单
元素接口应尽可能简单,只包含必要的方法,过于复杂的元素接口可能会导致访问者类也变得复杂。
2.使用访问者接口
即使只有一个具体访问者类,也应该定义一个访问者接口,这样可以在将来添加新的访问者类时,不需要修改元素类。
3.考虑使用默认实现
如果有多个访问者类,并且它们对某些元素类型的处理逻辑相似,可以考虑在访问者接口中提供默认实现,或者创建一个抽象访问者类。
less
public abstract class BaseVisitor implements Visitor{
@Override
public void visit(ConcreteElementA element){
// 默认实现
}
@Override
public void visit(ConcreteElementB element){
// 默认实现
}
}
4.避免修改元素的状态
访问者应该主要用于执行操作,而不是修改元素的状态,如果需要修改元素的状态,应该谨慎考虑,并确保不会破坏元素的一致性。