虚函数与设计模式:多态的真正舞台
说实话,很多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();
}
};
你看,每个工厂只负责创建一种产品。新增产品时,只需要添加新的产品和对应的工厂类,完全不用改已有代码。这就是开闭原则的体现。
策略模式中的多态
策略模式,说白了就是把算法封装起来,让它们可以互相替换。虚函数在这里扮演了"算法接口"的角色。
我记得有一次做支付系统,需要支持微信、支付宝、银联三种支付方式。如果用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方法。这就是多态的魅力——让调用者不需要知道被调用者的具体类型。
模板方法模式
模板方法模式有点特别。它不是在运行时多态,而是在编译时就确定了调用关系。但它的实现依然依赖虚函数。
模板方法的核心思想是:把算法的骨架放在基类,具体步骤交给子类实现。基类中的模板方法调用虚函数,子类重写这些虚函数来定制行为。
// 游戏基类
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;
}
};
你可能会问:这和策略模式有什么区别?区别在于:模板方法控制的是流程,策略模式控制的是算法。模板方法中,基类决定了"什么时候做什么",子类决定"具体怎么做"。
四种模式对比
| 模式 | 多态类型 | 虚函数作用 | 典型场景 |
|---|---|---|---|
| 工厂模式 | 运行时多态 | 创建对象 | 对象创建、产品族 |
| 策略模式 | 运行时多态 | 封装算法 | 支付、排序、压缩 |
| 观察者模式 | 运行时多态 | 统一通知接口 | 事件处理、消息推送 |
| 模板方法 | 编译时多态 | 定义步骤接口 | 框架设计、流程控制 |
知识体系图
这张图展示了虚函数在四种设计模式中的不同角色。你会发现,虽然都是多态,但每种模式对虚函数的利用角度都不一样。
好了,这一章的内容就到这里。记住:多态不是目的,而是手段。真正重要的是,你用它来解决什么问题。
公众号:蓝海资料掘金营,微信deep3321