访问者模式实战:别再让代码耦合到死!手把手教你解耦

📕目录

前言

一、为什么需要访问者模式?先看一个真实痛点

二、访问者模式的核心结构:5个角色拆解

三、C++实战:用访问者模式重构电商商品系统

第一步:定义抽象元素和抽象访问者(核心接口)

第二步:实现具体元素(三种商品类型)

第三步:实现具体访问者(统计、导出功能)

第四步:实现对象结构(商品管理类)

第五步:客户端调用(测试重构效果)

四、访问者模式的进阶:变体与优化技巧

1. 反射优化访问者(解决元素扩展问题)

2. Lambda简化访问者(适合简单操作)

五、访问者模式的利弊与适用场景

核心优点

主要缺点

黄金适用场景

绝对不适用场景

六、总结:访问者模式的设计哲学


class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

前言

在C++开发的江湖里,我们总会遇到这样的困境:一个稳定的对象结构(比如电商系统的商品体系、编译器的抽象语法树),却要频繁添加各种跨对象的操作(比如统计价格、导出数据、权限校验)。如果直接在对象类里堆代码,很快就会陷入"修改一个类,牵一发而动全身"的泥潭——这就是典型的"数据结构与操作耦合"绝症。

今天要聊的访问者模式,就是专治这种绝症的良方。它像一位灵活的巡检员,能在不改动对象本身的前提下,对对象结构里的每个元素执行定制化操作。本文会从实际开发场景出发,用C++代码手把手带你吃透访问者模式,从基础结构到进阶优化,再到避坑指南,全流程无死角解析。

一、为什么需要访问者模式?先看一个真实痛点

假设我们在开发一个电商后台的商品管理系统,系统中有三种核心商品类型:电子产品(手机、电脑)、服装(T恤、裤子)、食品(面包、牛奶)。每种商品都有名称、价格等基础属性,现在需要实现两个功能:

  1. 统计所有商品的总价(财务对账用)

  2. 生成商品的JSON导出数据(前端展示用)

如果不用设计模式,大多数人会这么写代码(新手常见写法):

#include <iostream>
#include <vector>
#include <string>
#include <nlohmann/json.hpp> // 假设使用nlohmann/json库

using namespace std;
using json = nlohmann::json;

// 电子产品类
class ElectronicProduct {
private:
    string name;
    double price;
    // 电子产品特有属性:保修年限
    int warrantyYears;
public:
    ElectronicProduct(string n, double p, int w) : name(n), price(p), warrantyYears(w) {}
    // 获取基础属性
    string getName() const { return name; }
    double getPrice() const { return price; }
    int getWarrantyYears() const { return warrantyYears; }
    
    // 功能1:计算价格(直接集成)
    double calculatePrice() const { return price; }
    // 功能2:生成JSON(直接集成)
    json toJson() const {
        return json{{"type", "electronic"}, {"name", name}, {"price", price}, {"warranty", warrantyYears}};
    }
};

// 服装类
class Clothing {
private:
    string name;
    double price;
    // 服装特有属性:尺码
    string size;
public:
    Clothing(string n, double p, string s) : name(n), price(p), size(s) {}
    string getName() const { return name; }
    double getPrice() const { return price; }
    string getSize() const { return size; }
    
    // 同样集成两个功能
    double calculatePrice() const { return price; }
    json toJson() const {
        return json{{"type", "clothing"}, {"name", name}, {"price", price}, {"size", size}};
    }
};

// 食品类
class Food {
private:
    string name;
    double price;
    // 食品特有属性:保质期
    int shelfLifeDays;
public:
    Food(string n, double p, int s) : name(n), price(p), shelfLifeDays(s) {}
    string getName() const { return name; }
    double getPrice() const { return price; }
    int getShelfLifeDays() const { return shelfLifeDays; }
    
    double calculatePrice() const { return price; }
    json toJson() const {
        return json{{"type", "food"}, {"name", name}, {"price", price}, {"shelfLife", shelfLifeDays}};
    }
};

// 商品管理类(对象结构)
class ProductManager {
private:
    vector<ElectronicProduct*> electronics;
    vector<Clothing*> clothings;
    vector<Food*> foods;
public:
    // 添加商品的方法(三种类型各一个)
    void addElectronic(ElectronicProduct* ep) { electronics.push_back(ep); }
    void addClothing(Clothing* c) { clothings.push_back(c); }
    void addFood(Food* f) { foods.push_back(f); }
    
    // 统计总价(需要遍历所有类型)
    double calculateTotalPrice() {
        double total = 0;
        for (auto ep : electronics) total += ep->calculatePrice();
        for (auto c : clothings) total += c->calculatePrice();
        for (auto f : foods) total += f->calculatePrice();
        return total;
    }
    
    // 导出所有商品JSON(同样遍历所有类型)
    json exportAllJson() {
        json result = json::array();
        for (auto ep : electronics) result.push_back(ep->toJson());
        for (auto c : clothings) result.push_back(c->toJson());
        for (auto f : foods) result.push_back(f->toJson());
        return result;
    }
};

// 客户端代码
int main() {
    ProductManager pm;
    // 添加商品
    pm.addElectronic(new ElectronicProduct("iPhone 15", 9999, 1));
    pm.addClothing(new Clothing("Cotton T-shirt", 99, "M"));
    pm.addFood(new Food("Milk Bread", 8.8, 3));
    
    // 统计总价
    cout << "Total Price: " << pm.calculateTotalPrice() << endl;
    // 导出JSON
    cout << "All Products JSON: " << pm.exportAllJson().dump(4) << endl;
    
    // 这里省略内存释放代码,实际开发需注意
    return 0;
}

这段代码能跑通,但如果你是维护者,很快会发现三个致命问题:

1. 功能扩展灾难:如果新增"生成Excel报表"功能,需要修改ElectronicProduct、Clothing、Food三个类,再修改ProductManager——违反"开闭原则"(对扩展开放,对修改关闭)。

2. 代码冗余混乱:统计价格、导出数据这些跨对象的操作,被分散在各个商品类中,后续排查bug或修改逻辑时,需要在多个类之间跳来跳去。

3. 对象结构僵化:ProductManager中为每种商品类型都写了容器和遍历逻辑,新增商品类型(比如化妆品)时,需要大幅修改ProductManager的代码。

而访问者模式的核心思想,就是把"数据结构"和"操作"彻底分离——让商品类只保留自身属性和接受访问的接口,把统计、导出这些操作封装成独立的"访问者"类。这样新增操作时,只需要加新的访问者,不用改商品类;商品类新增时,也只需扩展访问者接口,逻辑清晰且符合开闭原则。

二、访问者模式的核心结构:5个角色拆解

访问者模式是一种行为型设计模式,它的核心是通过"双重分派"机制,实现访问者对不同元素的差异化操作。官方定义的5个角色看似抽象,结合上面的电商场景一对应就很容易理解:

角色名称

核心职责

电商场景对应

抽象访问者(Visitor)

定义访问所有具体元素的接口,为每种元素类型声明一个visit方法

声明访问电子产品、服装、食品的接口(visitElectronic等)

具体访问者(ConcreteVisitor)

实现抽象访问者的接口,定义对每种元素的具体操作逻辑

统计总价访问者(实现计算每种商品价格的逻辑)、JSON导出访问者(实现每种商品的JSON转换)

抽象元素(Element)

声明接受访问者的接口(accept方法),将自身作为参数传给访问者

商品抽象类(声明accept方法)

具体元素(ConcreteElement)

实现抽象元素的accept方法,调用访问者对应的visit方法

电子产品、服装、食品类(实现accept方法,调用visitor->visit(this))

对象结构(ObjectStructure)

管理元素集合,提供遍历接口供访问者访问所有元素

商品管理类(管理所有商品,提供accept方法让访问者遍历访问)

这5个角色的协作流程可以总结为三句话:

  1. 对象结构持有所有具体元素,当需要执行操作时,接收一个具体访问者

  2. 对象结构遍历所有元素,调用每个元素的accept方法,并将访问者传入

  3. 每个元素的accept方法调用访问者对应的visit方法,完成访问者对该元素的操作

这里的关键是"双重分派":第一次分派是对象结构调用元素的accept方法(确定元素类型),第二次分派是元素调用访问者的visit方法(确定访问者类型)。通过这两次分派,就能精准匹配"访问者-元素"的组合,执行对应的操作。

三、C++实战:用访问者模式重构电商商品系统

下面我们用访问者模式重构开篇的电商系统,解决代码耦合问题。整个重构过程分四步走,每一步都有完整代码和注释,新手也能轻松跟上。

第一步:定义抽象元素和抽象访问者(核心接口)

首先定义商品抽象类(抽象元素)和访问者抽象类(抽象访问者)。抽象元素必须包含accept方法,用于接收访问者;抽象访问者必须为每种具体商品类型定义visit方法。

#include <iostream>
#include <vector>
#include <string>
#include <nlohmann/json.hpp>
#include <memory> // 用于智能指针,避免内存泄漏

using namespace std;
using json = nlohmann::json;

// 前向声明:抽象访问者需要知道具体元素类型,避免循环依赖
class ElectronicProduct;
class Clothing;
class Food;

// 抽象访问者(Visitor):定义访问所有商品的接口
class ProductVisitor {
public:
    // 纯虚函数,必须由具体访问者实现
    // 为每种商品类型声明一个visit方法
    virtual void visit(ElectronicProduct& product) = 0;
    virtual void visit(Clothing& product) = 0;
    virtual void visit(Food& product) = 0;
    
    // 虚析构函数,确保子类析构时能正确调用
    virtual ~ProductVisitor() = default;
};

// 抽象元素(Element):商品抽象类
class Product {
public:
    // 接受访问者的核心方法
    virtual void accept(ProductVisitor& visitor) = 0;
    
    // 商品通用属性(封装)
    virtual string getName() const = 0;
    virtual double getPrice() const = 0;
    
    virtual ~Product() = default;
};

这里有两个关键注意点:

  • 前向声明:由于抽象访问者的visit方法参数是具体商品类型,而具体商品类型又依赖抽象访问者,所以需要用前向声明打破循环依赖。

  • 智能指针:后续使用shared_ptr管理对象,避免手动释放内存的麻烦,这是C++现代开发的最佳实践。

第二步:实现具体元素(三种商品类型)

具体商品类继承自Product抽象类,实现accept方法和自身特有属性。accept方法的实现非常固定,就是调用访问者对应的visit方法,并将自身作为参数传入——这是触发双重分派的关键一步。

// 具体元素1:电子产品
class ElectronicProduct : public Product {
private:
    string name;
    double price;
    int warrantyYears; // 特有属性:保修年限
public:
    ElectronicProduct(string n, double p, int w) 
        : name(n), price(p), warrantyYears(w) {}
    
    // 实现accept方法:调用访问者的visit(ElectronicProduct)
    void accept(ProductVisitor& visitor) override {
        visitor.visit(*this); // 把自身传给访问者
    }
    
    // 实现通用属性接口
    string getName() const override { return name; }
    double getPrice() const override { return price; }
    
    // 特有属性的访问接口(供访问者使用)
    int getWarrantyYears() const { return warrantyYears; }
};

// 具体元素2:服装
class Clothing : public Product {
private:
    string name;
    double price;
    string size; // 特有属性:尺码
public:
    Clothing(string n, double p, string s) 
        : name(n), price(p), size(s) {}
    
    void accept(ProductVisitor& visitor) override {
        visitor.visit(*this);
    }
    
    string getName() const override { return name; }
    double getPrice() const override { return price; }
    
    string getSize() const { return size; }
};

// 具体元素3:食品
class Food : public Product {
private:
    string name;
    double price;
    int shelfLifeDays; // 特有属性:保质期(天)
public:
    Food(string n, double p, int s) 
        : name(n), price(p), shelfLifeDays(s) {}
    
    void accept(ProductVisitor& visitor) override {
        visitor.visit(*this);
    }
    
    string getName() const override { return name; }
    double getPrice() const override { return price; }
    
    int getShelfLifeDays() const { return shelfLifeDays; }
};

对比重构前的代码,现在的商品类变得非常"纯粹"——只包含自身属性和接受访问的接口,没有任何统计、导出等业务逻辑。这就是访问者模式"分离数据与操作"的核心体现。

第三步:实现具体访问者(统计、导出功能)

接下来把统计总价和导出JSON这两个功能,分别封装成两个具体访问者类。每个访问者实现抽象访问者的所有visit方法,针对不同商品类型编写差异化逻辑。

// 具体访问者1:统计总价访问者
class TotalPriceVisitor : public ProductVisitor {
private:
    double totalPrice; // 累积状态:总价格
public:
    TotalPriceVisitor() : totalPrice(0.0) {}
    
    // 访问电子产品:累加价格
    void visit(ElectronicProduct& product) override {
        totalPrice += product.getPrice();
        // 可以添加电子产品特有的逻辑,比如高端电子产品加税
        if (product.getPrice() > 5000) {
            totalPrice += product.getPrice() * 0.05; // 5%税
        }
    }
    
    // 访问服装:直接累加价格
    void visit(Clothing& product) override {
        totalPrice += product.getPrice();
    }
    
    // 访问食品:累加价格,可添加临期食品折扣逻辑
    void visit(Food& product) override {
        double price = product.getPrice();
        if (product.getShelfLifeDays() <= 1) {
            price *= 0.5; // 临期食品5折
        }
        totalPrice += price;
    }
    
    // 获取统计结果
    double getTotalPrice() const {
        return totalPrice;
    }
    
    // 重置统计状态(方便重复使用)
    void reset() {
        totalPrice = 0.0;
    }
};

// 具体访问者2:JSON导出访问者
class JsonExportVisitor : public ProductVisitor {
private:
    json result; // 累积状态:导出的JSON数据
public:
    JsonExportVisitor() : result(json::array()) {}
    
    // 访问电子产品:生成包含保修信息的JSON
    void visit(ElectronicProduct& product) override {
        json item = {
            {"type", "electronic"},
            {"name", product.getName()},
            {"price", product.getPrice()},
            {"warranty_years", product.getWarrantyYears()}
        };
        result.push_back(item);
    }
    
    // 访问服装:生成包含尺码的JSON
    void visit(Clothing& product) override {
        json item = {
            {"type", "clothing"},
            {"name", product.getName()},
            {"price", product.getPrice()},
            {"size", product.getSize()}
        };
        result.push_back(item);
    }
    
    // 访问食品:生成包含保质期的JSON
    void visit(Food& product) override {
        json item = {
            {"type", "food"},
            {"name", product.getName()},
            {"price", product.getPrice()},
            {"shelf_life_days", product.getShelfLifeDays()}
        };
        result.push_back(item);
    }
    
    // 获取JSON结果(带格式化)
    string getJsonString() const {
        return result.dump(4); // 4个空格缩进,便于阅读
    }
    
    // 重置导出状态
    void reset() {
        result = json::array();
    }
};

这里的亮点是"状态累积"——访问者可以在遍历过程中保存操作产生的状态(比如总价格、JSON数组),这在统计、汇总类场景中非常实用。而且每个访问者的逻辑独立封装,修改统计规则时只需改TotalPriceVisitor,不影响其他类。

第四步:实现对象结构(商品管理类)

对象结构的核心职责是管理所有商品对象,并提供一个accept方法,让访问者可以遍历访问所有商品。重构后的商品管理类不再需要为每种商品单独写容器,只需一个存储Product智能指针的向量即可。

// 对象结构(ObjectStructure):商品管理类
class ProductManager {
private:
    // 存储商品抽象类的智能指针,支持所有商品类型
    vector<shared_ptr<Product>> products;
public:
    // 添加商品(支持任意商品类型,自动多态)
    void addProduct(shared_ptr<Product> product) {
        products.push_back(product);
    }
    
    // 核心方法:接受访问者,遍历所有商品让访问者访问
    void accept(ProductVisitor& visitor) {
        for (auto& product : products) {
            // 调用商品的accept方法,触发访问者的visit
            product->accept(visitor);
        }
    }
    
    // 清空商品(可选)
    void clearProducts() {
        products.clear();
    }
};

对比重构前的代码,现在的ProductManager变得异常简洁:新增商品类型时,不需要修改任何代码,直接addProduct即可;遍历逻辑也只需写一次,彻底解决了对象结构僵化的问题。

第五步:客户端调用(测试重构效果)

客户端代码只需创建商品、访问者和对象结构,通过对象结构的accept方法触发访问者操作,逻辑清晰易懂。我们还可以轻松新增访问者(比如Excel导出访问者),无需修改现有代码。

int main() {
    // 1. 创建对象结构(商品管理器)
    ProductManager pm;
    
    // 2. 添加商品(多态特性:子类指针赋给父类指针)
    pm.addProduct(make_shared<ElectronicProduct>("iPhone 15", 9999, 1));
    pm.addProduct(make_shared<Clothing>("Cotton T-shirt", 99, "M"));
    pm.addProduct(make_shared<Food>("Milk Bread", 8.8, 3));
    pm.addProduct(make_shared<Food>("Yogurt", 5.0, 1)); // 临期食品(保质期1天)
    
    // 3. 统计总价:使用TotalPriceVisitor
    TotalPriceVisitor priceVisitor;
    pm.accept(priceVisitor); // 触发访问者遍历所有商品
    cout << "=== 商品总价统计 ===" << endl;
    cout << "Total Price (including tax and discount): " << priceVisitor.getTotalPrice() << endl;
    cout << endl;
    
    // 4. 导出JSON:使用JsonExportVisitor
    JsonExportVisitor jsonVisitor;
    pm.accept(jsonVisitor); // 触发访问者遍历所有商品
    cout << "=== 商品JSON导出 ===" << endl;
    cout << jsonVisitor.getJsonString() << endl;
    
    // 5. 新增操作:无需修改现有类,直接加新访问者即可
    // 比如新增ExcelExportVisitor,此处省略实现...
    
    return 0;
}

运行结果如下(包含临期食品折扣和高端电子产品税):

=== 商品总价统计 ===
Total Price (including tax and discount): 10577.7

=== 商品JSON导出 ===
[
    {
        "name": "iPhone 15",
        "price": 9999.0,
        "type": "electronic",
        "warranty_years": 1
    },
    {
        "name": "Cotton T-shirt",
        "price": 99.0,
        "size": "M",
        "type": "clothing"
    },
    {
        "name": "Milk Bread",
        "price": 8.8,
        "shelf_life_days": 3,
        "type": "food"
    },
    {
        "name": "Yogurt",
        "price": 5.0,
        "shelf_life_days": 1,
        "type": "food"
    }
]

重构后的代码完美解决了开篇的三个问题:新增操作只需加访问者,商品类无需修改;操作逻辑集中在访问者中,无冗余;新增商品类型只需扩展访问者接口,对象结构无需改动。

四、访问者模式的进阶:变体与优化技巧

上面的基础实现虽然解决了核心问题,但在实际开发中还有一些场景需要优化。比如新增商品类型时,需要修改抽象访问者的接口,这违反了开闭原则。下面介绍两种C++中常用的访问者变体,解决这些痛点。

1. 反射优化访问者(解决元素扩展问题)

基础实现中,新增商品类型(如Cosmetic化妆品)时,需要修改ProductVisitor接口,添加visit(Cosmetic&)方法,这会导致所有具体访问者都要同步修改。利用C++的RTTI(运行时类型识别)特性,可以实现"动态访问者",无需修改接口即可支持新元素。

#include <typeinfo> // 用于typeid

// 反射式抽象访问者:无需为每种元素声明visit方法
class ReflectiveProductVisitor {
public:
    virtual ~ReflectiveProductVisitor() = default;
    
    // 核心方法:根据元素类型动态调用对应的visit方法
    void visit(Product& product) {
        // 通过typeid获取元素的实际类型
        const type_info& type = typeid(product);
        if (type == typeid(ElectronicProduct)) {
            visitDynamic(dynamic_cast<ElectronicProduct&>(product));
        } else if (type == typeid(Clothing)) {
            visitDynamic(dynamic_cast<Clothing&>(product));
        } else if (type == typeid(Food)) {
            visitDynamic(dynamic_cast<Food&>(product));
        } else {
            // 处理未知类型(默认操作)
            visitUnknown(product);
        }
    }
    
    // 具体元素的访问方法(子类实现)
    virtual void visitDynamic(ElectronicProduct& product) = 0;
    virtual void visitDynamic(Clothing& product) = 0;
    virtual void visitDynamic(Food& product) = 0;
    
    // 未知元素的默认处理
    virtual void visitUnknown(Product& product) {
        cout << "Unknown product type: " << product.getName() << endl;
    }
};

// 具体反射访问者:简化的统计访问者
class SimplePriceVisitor : public ReflectiveProductVisitor {
private:
    double total;
public:
    SimplePriceVisitor() : total(0) {}
    
    void visitDynamic(ElectronicProduct& product) override {
        total += product.getPrice();
    }
    
    void visitDynamic(Clothing& product) override {
        total += product.getPrice();
    }
    
    void visitDynamic(Food& product) override {
        total += product.getPrice();
    }
    
    double getTotal() const { return total; }
};

这种方式的优点是新增商品类型时,只需修改ReflectiveProductVisitor的visit方法,无需改动所有具体访问者;缺点是使用dynamic_cast有轻微性能开销,且类型判断依赖typeid,不如编译期检查严格。适合元素类型频繁新增的场景。

2. Lambda简化访问者(适合简单操作)

对于一些简单的临时操作,每次都创建一个具体访问者类太繁琐。可以利用C++11及以上的Lambda表达式,实现"匿名访问者",大幅简化代码。

#include <functional>
#include <unordered_map>

// Lambda访问者:通过注册Lambda表达式处理不同元素
class LambdaProductVisitor : public ProductVisitor {
private:
    // 存储"元素类型信息 - Lambda处理函数"的映射
    using ElectronicHandler = function<void(ElectronicProduct&)>;
    using ClothingHandler = function<void(Clothing&)>;
    using FoodHandler = function<void(Food&)>;
    
    ElectronicHandler electronicHandler;
    ClothingHandler clothingHandler;
    FoodHandler foodHandler;
    
public:
    // 注册各元素的处理函数
    void onElectronic(ElectronicHandler handler) {
        electronicHandler = handler;
    }
    
    void onClothing(ClothingHandler handler) {
        clothingHandler = handler;
    }
    
    void onFood(FoodHandler handler) {
        foodHandler = handler;
    }
    
    // 实现访问者接口,调用对应的Lambda
    void visit(ElectronicProduct& product) override {
        if (electronicHandler) electronicHandler(product);
    }
    
    void visit(Clothing& product) override {
        if (clothingHandler) clothingHandler(product);
    }
    
    void visit(Food& product) override {
        if (foodHandler) foodHandler(product);
    }
};

// 客户端使用示例:临时统计食品数量
int main() {
    ProductManager pm;
    pm.addProduct(make_shared<Food>("Bread", 3.5, 7));
    pm.addProduct(make_shared<Food>("Milk", 6.0, 5));
    
    // 使用Lambda访问者统计食品数量
    int foodCount = 0;
    LambdaProductVisitor lambdaVisitor;
    
    // 注册食品的处理逻辑(Lambda表达式)
    lambdaVisitor.onFood([&foodCount](Food& product) {
        foodCount++;
        cout << "Found food: " << product.getName() << endl;
    });
    
    // 其他商品类型的处理逻辑可选注册
    lambdaVisitor.onElectronic([](ElectronicProduct& product) {
        // 空实现或默认处理
    });
    
    pm.accept(lambdaVisitor);
    cout << "Total food count: " << foodCount << endl; // 输出2
    
    return 0;
}

这种方式特别适合临时的、简单的操作,无需定义单独的访问者类,代码更紧凑。但对于复杂的、需要状态累积的操作,还是传统访问者模式更清晰。

五、访问者模式的利弊与适用场景

没有完美的设计模式,只有适合的场景。访问者模式的优缺点非常鲜明,实际开发中需要根据业务场景权衡使用。

核心优点

  1. 解耦数据与操作:元素类只保留自身数据,操作被封装在访问者中,符合单一职责原则。

  2. 扩展灵活:新增操作只需添加新访问者,无需修改元素类,完美符合开闭原则。

  3. 操作集中化:同一类操作的代码集中在一个访问者中,便于维护和调试(比如所有统计相关逻辑都在PriceVisitor中)。

  4. 支持复杂遍历:对象结构可以提供多种遍历方式(如深度优先、广度优先),访问者无需关心遍历细节。

主要缺点

  1. 元素扩展困难:新增元素类型时,需要修改所有访问者的接口和实现,违反开闭原则。

  2. 破坏封装性:访问者需要访问元素的内部属性,可能导致元素类暴露过多的访问接口。

  3. 依赖具体类型:访问者需要知道所有具体元素的类型,导致系统耦合度升高(抽象访问者依赖具体元素)。

  4. 学习成本高:双重分派机制相对抽象,新手不易理解和掌握。

黄金适用场景

1. 对象结构稳定,但操作频繁变化:比如编译器的抽象语法树(AST),语法树结构固定,但需要频繁添加词法分析、语义分析、代码生成等操作。

2. 需要对对象结构中的元素执行多种不相关的操作:比如文档编辑器中的元素(文本、图片、表格),需要执行导出PDF、统计字数、格式检查等操作。

3. 需要跨多个元素累积状态:比如电商系统中的订单统计(统计所有商品总价、数量、重量)。

4. 元素类之间差异较大,且需要统一处理:比如不同类型的日志(访问日志、错误日志、业务日志),需要统一收集和分析。

绝对不适用场景

1. 对象结构频繁变化:比如初创公司的产品模块,商品类型每周都在新增,使用访问者模式会导致代码爆炸。

2. 操作与元素紧密耦合:比如元素的核心业务逻辑(如商品的库存扣减),不适合抽离为访问者。

3. 简单对象结构:比如只有一两种元素的系统,使用访问者模式会过度设计,增加代码复杂度。

六、总结:访问者模式的设计哲学

访问者模式的本质,是用"牺牲元素扩展性"换取"操作扩展性"。它是一种典型的"以空间换时间"、"以一种耦合换另一种耦合"的设计思想——在对象结构稳定的前提下,通过将操作抽离为访问者,实现操作的快速扩展。

在C++开发中使用访问者模式时,建议记住三个实战技巧:

  1. 优先用智能指针:管理元素对象的生命周期,避免内存泄漏(如本文示例中的shared_ptr)。

  2. 合理暴露接口:通过友元类或私有成员访问器,控制访问者对元素属性的访问权限,避免过度暴露。

  3. 结合其他模式优化:比如用工厂模式创建访问者,用单例模式管理全局访问者,用装饰器模式扩展访问者功能。

最后需要强调的是,设计模式不是银弹,不要为了用模式而用模式。在实际开发中,先写出能跑通的代码,再根据业务演进和代码痛点,逐步引入合适的设计模式——这才是优秀开发者的正确姿势。

如果你在使用访问者模式时遇到了具体问题,或者有更好的优化技巧,欢迎在评论区留言讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值