多态的运用
多态是面向对象程序设计中最核心、最强大的特性之一。它让同一段代码能够根据对象的实际类型表现出不同的行为,极大地提升了程序的灵活性、可扩展性和可维护性。
什么是多态
多态的基本概念
多态(Polymorphism) 源于希腊语,意为"多种形态"。在面向对象程序设计中,多态指的是同一个接口,多种实现,其核心是用同一段代码可以操作
继承层次中的一组类型(即Class Hierarchy),并自动调用适合该类型的方法。这使得代码更加通用、灵活和可扩展。
多态的本质
- 接口统一:所有对象通过相同的接口(基类指针/引用)被访问。
- 行为差异:不同对象对同一操作表现出不同的行为。
- 运行时决定:具体调用哪个版本的函数,在运行时根据对象的实际类型决定。
- 关注"做什么":调用者只关心"做什么",不关心"怎么做"。
多态的分类
编译时多态 vs 运行时多态
| 类型 |
实现方式 |
绑定时机 |
示例 |
| 编译时多态 |
函数重载、运算符重载、模板 |
编译时 |
cout << 10 vs cout << "hello" |
| 运行时多态 |
虚函数、继承 |
运行时 |
通过 Shape* 调用 draw() |
通常所说的"多态"主要指运行时多态,即通过虚函数实现的动态绑定机制。
多态的应用场景
场景一:统一处理异构对象集合
最经典的多态应用场景是将不同类型的对象放入同一个容器中,通过统一的接口进行批量操作。
统一处理异构集合
| #include <iostream>
#include <vector>
#include <memory>
using namespace std;
// 抽象基类
class Shape {
public:
virtual double area() const = 0;
virtual void draw() const = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
virtual double area() const override {
return 3.14159 * radius * radius;
}
virtual void draw() const override {
cout << "○ Circle (r=" << radius << ", area=" << area() << ")" << endl;
}
};
class Rectangle : public Shape {
private:
double w, h;
public:
Rectangle(double w, double h) : w(w), h(h) {}
virtual double area() const override {
return w * h;
}
virtual void draw() const override {
cout << "▭ Rectangle (" << w << "×" << h << ", area=" << area() << ")" << endl;
}
};
class Triangle : public Shape {
private:
double a, b, c;
public:
Triangle(double a, double b, double c) : a(a), b(b), c(c) {}
virtual double area() const override {
double s = (a + b + c) / 2;
return sqrt(s * (s - a) * (s - b) * (s - c));
}
virtual void draw() const override {
cout << "△ Triangle (sides=" << a << "," << b << "," << c
<< ", area=" << area() << ")" << endl;
}
};
int main() {
// 创建各种形状
Circle c1(5.0);
Circle c2(3.0);
Rectangle r1(4.0, 6.0);
Triangle t1(3.0, 4.0, 5.0);
// 使用动态数组管理异构对象集合
vector<Shape*> shapes;
shapes.push_back(&c1);
shapes.push_back(&c2);
shapes.push_back(&r1);
shapes.push_back(&t1);
// 统一处理:遍历并绘制所有形状
cout << "=== Drawing All Shapes ===" << endl;
for (const auto& shape : shapes) {
shape->draw(); // 运行时多态!
}
// 统一计算总面积
double totalArea = 0;
for (const auto& shape : shapes) {
totalArea += shape->area(); // 运行时多态!
}
cout << "\nTotal Area: " << totalArea << endl;
// 验证类型兼容
Shape* p = new Circle(2.0, "purple");
p->draw(); // 调用 Circle::draw()
delete p;
return 0;
}
|
场景二:依赖抽象而非具体实现(依赖倒置)
多态是实现依赖倒置原则(Dependency Inversion Principle) 的关键工具——高层模块不应依赖低层模块,两者都应依赖抽象。
依赖抽象编程
| #include <iostream>
#include <vector>
using namespace std;
// === 抽象接口 ===
class DataSource {
public:
virtual string getData() const = 0;
virtual ~DataSource() {}
};
class DataProcessor {
public:
// 依赖抽象:DataProcessor 不关心具体的数据源类型
void process(const DataSource& source) {
string data = source.getData(); // 多态调用
cout << "Processing: " << data << endl;
// ... 处理数据
}
void processAll(const vector<DataSource*>& sources) {
for (auto src : sources) {
process(*src);
}
}
};
// === 具体实现:各种数据源 ===
class FileSource : public DataSource {
private:
string filename;
public:
FileSource(const string& f) : filename(f) {}
virtual string getData() const override {
return "Data from file: " + filename;
}
};
class DatabaseSource : public DataSource {
private:
string query;
public:
DatabaseSource(const string& q) : query(q) {}
virtual string getData() const override {
return "Data from database: " + query;
}
};
class NetworkSource : public DataSource {
private:
string url;
public:
NetworkSource(const string& u) : url(u) {}
virtual string getData() const override {
return "Data from network: " + url;
}
};
int main() {
FileSource file("data.txt");
DatabaseSource db("SELECT * FROM users");
NetworkSource net("https://api.example.com");
DataProcessor processor;
// 统一处理不同类型的数据源
processor.process(file);
processor.process(db);
processor.process(net);
// 批量处理
cout << "\n=== Batch Processing ===" << endl;
vector<DataSource*> sources = {&file, &db, &net};
processor.processAll(sources);
return 0;
}
|
依赖抽象的优势
- 扩展性好:新增数据源只需实现
DataSource 接口,DataProcessor 无需修改。
- 可测试性强:可以用
MockDataSource 替代真实数据源进行单元测试。
- 模块解耦:处理器与具体数据源实现解耦,可独立开发和维护。
场景三:回调机制与钩子函数
多态常用来实现回调(Callback) 和钩子函数(Hook Function) ,让框架/库在特定时机调用用户自定义的行为。
框架中的钩子函数
| #include <iostream>
#include <chrono>
#include <thread>
using namespace std;
// === 应用框架:定义生命周期接口 ===
class Application {
public:
// 框架定义的生命周期钩子(默认空实现)
virtual void onStart() {
// 可选覆盖
}
virtual void onTick(int elapsed) {
// 可选覆盖
}
virtual void onStop() {
// 可选覆盖
}
// 框架的核心逻辑(模板方法)
void run(int seconds) {
onStart(); // 钩子:启动时调用
for (int i = 0; i < seconds; i++) {
onTick(i); // 钩子:每个周期调用
this_thread::sleep_for(chrono::seconds(1));
}
onStop(); // 钩子:结束时调用
}
virtual ~Application() {}
};
// === 用户自定义应用 ===
class TimerApp : public Application {
private:
int count = 0;
public:
virtual void onStart() override {
cout << "⏱️ Timer started!" << endl;
count = 0;
}
virtual void onTick(int elapsed) override {
count++;
cout << " Tick " << elapsed + 1 << ": " << count << " seconds elapsed" << endl;
}
virtual void onStop() override {
cout << "⏱️ Timer stopped! Total: " << count << " seconds" << endl;
}
};
class CounterApp : public Application {
public:
virtual void onStart() override {
cout << "🔢 Counter started!" << endl;
}
virtual void onTick(int elapsed) override {
for (int i = 0; i <= elapsed; i++) {
cout << ".";
}
cout << " " << elapsed + 1 << endl;
}
virtual void onStop() override {
cout << "🔢 Counter finished!" << endl;
}
};
int main() {
TimerApp timer;
cout << "--- Running TimerApp ---" << endl;
timer.run(5);
cout << "\n--- Running CounterApp ---" << endl;
CounterApp counter;
counter.run(8);
return 0;
}
|
设计模式中的应用
这种"框架定义钩子,用户实现钩子"的模式,是模板方法模式(Template Method Pattern) 的典型应用。框架(Application)定义了算法的骨架(run 函数),将具体步骤留给子类实现。
场景四:策略模式中的多态
多态是实现策略模式(Strategy Pattern) 的基础——将算法封装在独立的类中,运行时动态选择不同的算法策略。
策略模式:不同排序算法
| #include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// === 策略接口 ===
class SortingStrategy {
public:
virtual void sort(vector<int>& data) const = 0;
virtual string getName() const = 0;
virtual ~SortingStrategy() {}
};
// === 具体策略:冒泡排序 ===
class BubbleSort : public SortingStrategy {
public:
virtual void sort(vector<int>& data) const override {
for (size_t i = 0; i < data.size() - 1; i++) {
for (size_t j = 0; j < data.size() - i - 1; j++) {
if (data[j] > data[j + 1]) {
swap(data[j], data[j + 1]);
}
}
}
}
virtual string getName() const override {
return "Bubble Sort";
}
};
// === 具体策略:快速排序 ===
class QuickSort : public SortingStrategy {
public:
virtual void sort(vector<int>& data) const override {
quicksort(data, 0, data.size() - 1);
}
virtual string getName() const override {
return "Quick Sort";
}
private:
void quicksort(vector<int>& data, int left, int right) const {
if (left >= right) return;
int pivot = data[(left + right) / 2];
int i = left, j = right;
while (i <= j) {
while (data[i] < pivot) i++;
while (data[j] > pivot) j--;
if (i <= j) {
swap(data[i], data[j]);
i++; j--;
}
}
quicksort(data, left, j);
quicksort(data, i, right);
}
};
// === 具体策略:标准库排序 ===
class StdSort : public SortingStrategy {
public:
virtual void sort(vector<int>& data) const override {
std::sort(data.begin(), data.end());
}
virtual string getName() const override {
return "Standard Sort";
}
};
// === 使用策略的上下文 ===
class Sorter {
public:
void setStrategy(const SortingStrategy& s) {
strategy = &s;
}
void sortAndPrint(vector<int> data) const {
if (!strategy) {
cout << "No strategy set!" << endl;
return;
}
cout << "Using: " << strategy->getName() << endl;
cout << " Before: ";
for (int x : data) cout << x << " ";
cout << endl;
strategy->sort(data);
cout << " After: ";
for (int x : data) cout << x << " ";
cout << endl;
}
private:
const SortingStrategy* strategy = nullptr;
};
int main() {
vector<int> data = {64, 34, 25, 12, 22, 11, 90};
BubbleSort bubble;
QuickSort quick;
StdSort standard;
Sorter sorter;
sorter.setStrategy(bubble);
sorter.sortAndPrint(data);
sorter.setStrategy(quick);
sorter.sortAndPrint(data);
sorter.setStrategy(standard);
sorter.sortAndPrint(data);
return 0;
}
|
场景五:工厂模式中的多态
多态是实现工厂模式(Factory Pattern) 的关键——将对象的创建逻辑封装在工厂中,运行时决定创建哪种类型的对象。
工厂模式:创建不同类型的日志记录器
| #include <iostream>
#include <string>
#include <memory>
using namespace std;
// === 抽象产品 ===
class Logger {
public:
virtual void log(const string& message) = 0;
virtual ~Logger() {}
};
// === 具体产品:控制台日志 ===
class ConsoleLogger : public Logger {
public:
virtual void log(const string& message) override {
cout << "[CONSOLE] " << message << endl;
}
};
// === 具体产品:文件日志 ===
class FileLogger : public Logger {
private:
string filename;
public:
FileLogger(const string& f) : filename(f) {}
virtual void log(const string& message) override {
cout << "[FILE: " << filename << "] " << message << endl;
}
};
// === 具体产品:远程日志 ===
class RemoteLogger : public Logger {
private:
string endpoint;
public:
RemoteLogger(const string& e) : endpoint(e) {}
virtual void log(const string& message) override {
cout << "[REMOTE: " << endpoint << "] " << message << endl;
}
};
// === 抽象工厂 ===
class LoggerFactory {
public:
virtual unique_ptr<Logger> createLogger() const = 0;
virtual ~LoggerFactory() {}
};
// === 具体工厂 ===
class ConsoleLoggerFactory : public LoggerFactory {
public:
virtual unique_ptr<Logger> createLogger() const override {
return make_unique<ConsoleLogger>();
}
};
class FileLoggerFactory : public LoggerFactory {
private:
string filename;
public:
FileLoggerFactory(const string& f) : filename(f) {}
virtual unique_ptr<Logger> createLogger() const override {
return make_unique<FileLogger>(filename);
}
};
class RemoteLoggerFactory : public LoggerFactory {
private:
string endpoint;
public:
RemoteLoggerFactory(const string& e) : endpoint(e) {}
virtual unique_ptr<Logger> createLogger() const override {
return make_unique<RemoteLogger>(endpoint);
}
};
// === 使用工厂 ===
class Application {
public:
void run(const LoggerFactory& factory) {
auto logger = factory.createLogger(); // 多态创建
logger->log("Application started");
logger->log("Processing data...");
logger->log("Application finished");
}
};
int main() {
Application app;
cout << "=== Console Mode ===" << endl;
ConsoleLoggerFactory consoleFactory;
app.run(consoleFactory);
cout << "\n=== File Mode ===" << endl;
FileLoggerFactory fileFactory("app.log");
app.run(fileFactory);
cout << "\n=== Remote Mode ===" << endl;
RemoteLoggerFactory remoteFactory("https://log.example.com");
app.run(remoteFactory);
return 0;
}
|
工厂模式的价值
- 解耦:客户端代码不依赖具体产品类,只依赖抽象接口。
- 可扩展:新增日志类型只需新增对应的产品类和工厂类,无需修改现有代码。
- 集中管理:对象的创建逻辑集中,便于维护和控制。
多态的扩展性与可维护性
新增类型的对比
多态最大的优势在于无需修改已有代码即可扩展系统。
多态带来的扩展性
| // 假设系统已有 Shape 层次
// 现在需要新增菱形 Diamond
class Diamond : public Shape {
private:
double d1, d2;
public:
Diamond(double d1, double d2) : d1(d1), d2(d2) {}
virtual double area() const override { return d1 * d2 / 2; }
virtual void draw() const override {
cout << "◇ Diamond (diagonals=" << d1 << "," << d2 << ")" << endl;
}
};
int main() {
// 现有代码无需任何修改即可支持新类型
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Diamond>(4.0, 6.0));
for (const auto& s : shapes) {
s->draw(); // 自动支持 Diamond!
}
return 0;
}
|
开闭原则的体现
多态天然支持开闭原则(Open-Closed Principle) :
- 对扩展开放:可以通过添加新的派生类来扩展系统功能。
- 对修改关闭:已有代码(基类、调用代码)无需修改。
条件分支 vs 多态
多态是实现用多态替代条件分支这一重构手法的核心工具。
条件分支的局限性
// 不使用多态:到处是条件分支
void draw(ShapeType type, void* shape) {
if (type == CIRCLE) {
drawCircle(*(Circle*)shape);
} else if (type == RECTANGLE) {
drawRectangle(*(Rectangle*)shape);
} else if (type == TRIANGLE) {
drawTriangle(*(Triangle*)shape);
}
// 每次新增类型都要修改这个函数!
}
多态的优势
// 使用多态:无需条件分支
void draw(const Shape& shape) {
shape.draw(); // 多态自动分发
}
// 新增类型无需修改 draw 函数!
多态的运行时开销
多态的代价
| 代价类型 |
说明 |
| 内存开销 |
每个对象多一个虚指针(通常 8 字节) |
| 性能开销 |
虚函数调用需要查虚表,比直接调用稍慢 |
| 对象大小 |
包含虚函数表的类,对象大小增加一个指针 |
| 编译限制 |
虚函数不能内联(内联是编译时展开,虚函数是运行时决定) |
何时使用多态
- 应该使用:需要处理多种类型、需要扩展性、需要运行时动态行为。
- 可以不使用:类型单一、性能敏感、简单的小型程序。
使用建议
多态使用建议
- 用基类指针/引用操作派生类对象:这是多态的基本用法。
- 基类析构函数声明为 virtual:避免资源泄漏。
- 使用 override 关键字:让编译器检查是否正确覆盖。
- 使用智能指针管理多态对象:避免手动 delete。
- 抽象类定义接口:在基类中使用纯虚函数定义规范。
- 避免在虚函数中使用默认参数:默认参数是静态绑定的。
小结
-
多态的本质:同一个接口,多种实现。通过基类指针/引用调用虚函数,运行时决定调用哪个版本。
-
多态的实现条件:
- 继承关系。
- 虚函数机制。
- 基类指针或引用。
- 派生类覆盖虚函数。
-
主要应用场景:
- 统一处理异构对象集合。
- 依赖抽象编程(依赖倒置)。
- 框架的回调与钩子函数。
- 策略模式(动态切换算法)。
- 工厂模式(动态创建对象)。
-
核心价值:
- 扩展性:新增类型无需修改已有代码(开闭原则)。
- 可维护性:减少条件分支,代码更清晰。
- 可重用性:统一接口处理多种类型。
-
代价与权衡:轻微的性能和内存开销,但对于大多数应用是可接受的。在需要扩展性和灵活性的场景中,多态的收益远大于代价。