虚函数与设计模式:多态的真正舞台

说实话,很多C++程序员学了虚函数,却不知道它到底有什么用。直到你开始接触设计模式,才会恍然大悟——哦,原来多态是这么玩的。

我在项目中重构过一个遗留系统,里面充斥着大量的if-else和switch-case。每次加新功能,都要改好几个地方,改完还提心吊胆。后来我用多态+设计模式重写了核心模块,代码量减少了40%,扩展起来轻松多了。

今天我们就来看看,虚函数在四种经典设计模式中是怎么发挥作用的。

工厂模式中的多态

工厂模式的核心思想是:让父类指针指向子类对象。这不就是多态的基本用法吗?

我习惯把工厂模式分成两种:简单工厂和抽象工厂。简单工厂用switch-case,抽象工厂用虚函数。你猜哪个更优雅?

核心要点:工厂模式利用虚函数实现对象的延迟创建,调用者不需要知道具体类型。
// 抽象产品
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() = default;
};

// 具体产品A
class ProductA : public Product {
public:
    void use() override {
        std::cout << "使用产品A" << std::endl;
    }
};

// 具体产品B
class ProductB : public Product {
public:
    void use() override {
        std::cout << "使用产品B" << std::endl;
    }
};

// 工厂基类
class Factory {
public:
    virtual Product* createProduct() = 0;
    virtual ~Factory() = default;
};

// 具体工厂A
class FactoryA : public Factory {
public:
    Product* createProduct() override {
        return new ProductA();
    }
};

// 具体工厂B
class FactoryB : public Factory {
public:
    Product* createProduct() override {
        return new ProductB();
    }
};

你看,每个工厂只负责创建一种产品。新增产品时,只需要添加新的产品和对应的工厂类,完全不用改已有代码。这就是开闭原则的体现。

我的经验:工厂模式配合虚函数,特别适合处理"产品族"的概念。比如UI框架中的按钮、文本框、下拉框,每个平台都有不同的实现。

策略模式中的多态

策略模式,说白了就是把算法封装起来,让它们可以互相替换。虚函数在这里扮演了"算法接口"的角色。

我记得有一次做支付系统,需要支持微信、支付宝、银联三种支付方式。如果用if-else,代码会变成一团乱麻。用策略模式就清爽多了。

// 支付策略接口
class PaymentStrategy {
public:
    virtual void pay(double amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// 微信支付
class WeChatPay : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "微信支付: " << amount << "元" << std::endl;
    }
};

// 支付宝支付
class Alipay : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "支付宝支付: " << amount << "元" << std::endl;
    }
};

// 上下文类
class PaymentContext {
private:
    PaymentStrategy* strategy;
public:
    void setStrategy(PaymentStrategy* s) {
        strategy = s;
    }
    
    void executePayment(double amount) {
        if (strategy) {
            strategy->pay(amount);
        }
    }
};

为什么说策略模式离不开虚函数?因为策略的切换发生在运行时,而虚函数正好提供了运行时多态的能力。你想想看,如果没有虚函数,你怎么在运行时动态切换算法?

我曾经踩过的坑:策略模式中的策略对象如果是有状态的,要注意生命周期管理。最好用智能指针,或者让上下文对象负责清理。

观察者模式中的多态

观察者模式,也叫发布-订阅模式。它的核心是:一个对象状态变化时,通知所有依赖它的对象

虚函数在这里的作用很明确:定义统一的更新接口。所有观察者都继承自同一个基类,重写update方法。

// 观察者接口
class Observer {
public:
    virtual void update(const std::string& message) = 0;
    virtual ~Observer() = default;
};

// 主题(被观察者)
class Subject {
private:
    std::vector<Observer*> observers;
public:
    void attach(Observer* obs) {
        observers.push_back(obs);
    }
    
    void notify(const std::string& msg) {
        for (auto obs : observers) {
            obs->update(msg);  // 多态调用
        }
    }
};

// 具体观察者A
class ObserverA : public Observer {
public:
    void update(const std::string& msg) override {
        std::cout << "观察者A收到: " << msg << std::endl;
    }
};

// 具体观察者B
class ObserverB : public Observer {
public:
    void update(const std::string& msg) override {
        std::cout << "观察者B收到: " << msg << std::endl;
    }
};

嗯,这里要注意:观察者模式中的虚函数调用是反向的。主题不知道观察者的具体类型,它只知道调用update方法。这就是多态的魅力——让调用者不需要知道被调用者的具体类型

实际应用场景:GUI事件处理、消息队列、股票行情推送、游戏中的成就系统。我做过一个实时监控系统,用观察者模式处理告警通知,效果非常好。

模板方法模式

模板方法模式有点特别。它不是在运行时多态,而是在编译时就确定了调用关系。但它的实现依然依赖虚函数。

模板方法的核心思想是:把算法的骨架放在基类,具体步骤交给子类实现。基类中的模板方法调用虚函数,子类重写这些虚函数来定制行为。

// 游戏基类
class Game {
public:
    // 模板方法 - 定义了游戏流程的骨架
    void play() {
        start();
        while (!isGameOver()) {
            takeTurn();
        }
        end();
    }
    
    virtual ~Game() = default;

protected:
    virtual void start() = 0;
    virtual bool isGameOver() = 0;
    virtual void takeTurn() = 0;
    virtual void end() = 0;
};

// 象棋游戏
class Chess : public Game {
protected:
    void start() override {
        std::cout << "象棋开始" << std::endl;
    }
    
    bool isGameOver() override {
        // 检查将/帅是否被吃
        return false;
    }
    
    void takeTurn() override {
        std::cout << "走一步棋" << std::endl;
    }
    
    void end() override {
        std::cout << "象棋结束" << std::endl;
    }
};

你可能会问:这和策略模式有什么区别?区别在于:模板方法控制的是流程,策略模式控制的是算法。模板方法中,基类决定了"什么时候做什么",子类决定"具体怎么做"。

我的建议:模板方法模式特别适合框架设计。比如MFC、Qt中的事件处理流程,都是用模板方法实现的。你只需要重写几个虚函数,就能定制行为。

四种模式对比

模式 多态类型 虚函数作用 典型场景
工厂模式 运行时多态 创建对象 对象创建、产品族
策略模式 运行时多态 封装算法 支付、排序、压缩
观察者模式 运行时多态 统一通知接口 事件处理、消息推送
模板方法 编译时多态 定义步骤接口 框架设计、流程控制

知识体系图

虚函数与多态 工厂模式 策略模式 观察者模式 模板方法 对象创建 父类指针指向 子类对象 延迟具体类型 算法封装 运行时切换 算法实现 消除if-else 事件通知 一对多依赖 状态变化通知 解耦发布订阅 流程控制 骨架固定 步骤可定制 框架设计利器

这张图展示了虚函数在四种设计模式中的不同角色。你会发现,虽然都是多态,但每种模式对虚函数的利用角度都不一样。

总结一下:虚函数是设计模式的基石。没有虚函数,这些模式要么无法实现,要么实现起来非常别扭。我个人觉得,理解虚函数的最佳方式,就是去实现这些设计模式。写一遍代码,比看十遍理论都管用。

好了,这一章的内容就到这里。记住:多态不是目的,而是手段。真正重要的是,你用它来解决什么问题。


公众号:蓝海资料掘金营,微信deep3321