C++ 接口
概述
接口(Interface)定义了类必须实现的方法规范,不包含具体实现。C++通过纯虚函数和抽象类实现接口概念。接口实现了契约式编程,提高了代码的可扩展性和可测试性。
🎯 接口基础
纯虚函数接口
cpp
#include <iostream>
#include <memory>
#include <vector>
// 基础接口定义
class Drawable {
public:
virtual ~Drawable() = default;
virtual void draw() const = 0;
virtual void move(int x, int y) = 0;
};
class Resizable {
public:
virtual ~Resizable() = default;
virtual void resize(double factor) = 0;
virtual double getArea() const = 0;
};
// 实现多个接口
class Circle : public Drawable, public Resizable {
private:
int x_, y_;
double radius_;
public:
Circle(int x, int y, double radius) : x_(x), y_(y), radius_(radius) {}
// 实现Drawable接口
void draw() const override {
std::cout << "绘制圆形,中心(" << x_ << "," << y_ << "),半径" << radius_ << std::endl;
}
void move(int x, int y) override {
x_ = x;
y_ = y;
std::cout << "圆形移动到(" << x_ << "," << y_ << ")" << std::endl;
}
// 实现Resizable接口
void resize(double factor) override {
radius_ *= factor;
std::cout << "圆形缩放,新半径: " << radius_ << std::endl;
}
double getArea() const override {
return 3.14159 * radius_ * radius_;
}
double getRadius() const { return radius_; }
};
class Rectangle : public Drawable, public Resizable {
private:
int x_, y_;
double width_, height_;
public:
Rectangle(int x, int y, double width, double height)
: x_(x), y_(y), width_(width), height_(height) {}
void draw() const override {
std::cout << "绘制矩形,位置(" << x_ << "," << y_ << "),大小"
<< width_ << "x" << height_ << std::endl;
}
void move(int x, int y) override {
x_ = x;
y_ = y;
std::cout << "矩形移动到(" << x_ << "," << y_ << ")" << std::endl;
}
void resize(double factor) override {
width_ *= factor;
height_ *= factor;
std::cout << "矩形缩放,新大小: " << width_ << "x" << height_ << std::endl;
}
double getArea() const override {
return width_ * height_;
}
};
int main() {
std::cout << "=== 接口基础 ===" << std::endl;
std::vector<std::unique_ptr<Drawable>> drawables;
drawables.push_back(std::make_unique<Circle>(0, 0, 5.0));
drawables.push_back(std::make_unique<Rectangle>(10, 10, 4.0, 6.0));
// 通过Drawable接口操作
for (auto& drawable : drawables) {
drawable->draw();
drawable->move(20, 30);
}
std::vector<std::unique_ptr<Resizable>> resizables;
resizables.push_back(std::make_unique<Circle>(0, 0, 3.0));
resizables.push_back(std::make_unique<Rectangle>(0, 0, 2.0, 4.0));
// 通过Resizable接口操作
std::cout << "\n--- 缩放操作 ---" << std::endl;
for (auto& resizable : resizables) {
std::cout << "原面积: " << resizable->getArea() << std::endl;
resizable->resize(1.5);
std::cout << "新面积: " << resizable->getArea() << std::endl;
}
return 0;
}🏗️ 接口设计模式
策略模式接口
cpp
#include <iostream>
#include <string>
#include <memory>
// 策略接口
class CompressionStrategy {
public:
virtual ~CompressionStrategy() = default;
virtual std::string compress(const std::string& data) = 0;
virtual std::string decompress(const std::string& data) = 0;
virtual std::string getName() const = 0;
};
// 具体策略实现
class ZipCompression : public CompressionStrategy {
public:
std::string compress(const std::string& data) override {
return "[ZIP压缩]" + data + "[/ZIP]";
}
std::string decompress(const std::string& data) override {
// 简化的解压缩
size_t start = data.find("]") + 1;
size_t end = data.find("[/");
return data.substr(start, end - start);
}
std::string getName() const override { return "ZIP"; }
};
class RarCompression : public CompressionStrategy {
public:
std::string compress(const std::string& data) override {
return "[RAR压缩]" + data + "[/RAR]";
}
std::string decompress(const std::string& data) override {
size_t start = data.find("]") + 1;
size_t end = data.find("[/");
return data.substr(start, end - start);
}
std::string getName() const override { return "RAR"; }
};
// 上下文类
class FileCompressor {
private:
std::unique_ptr<CompressionStrategy> strategy_;
public:
void setStrategy(std::unique_ptr<CompressionStrategy> strategy) {
strategy_ = std::move(strategy);
}
std::string compressFile(const std::string& data) {
if (!strategy_) {
throw std::runtime_error("未设置压缩策略");
}
std::cout << "使用" << strategy_->getName() << "压缩" << std::endl;
return strategy_->compress(data);
}
std::string decompressFile(const std::string& data) {
if (!strategy_) {
throw std::runtime_error("未设置压缩策略");
}
std::cout << "使用" << strategy_->getName() << "解压" << std::endl;
return strategy_->decompress(data);
}
};
int main() {
std::cout << "=== 策略模式接口 ===" << std::endl;
FileCompressor compressor;
std::string originalData = "Hello World! This is test data.";
// 使用ZIP策略
compressor.setStrategy(std::make_unique<ZipCompression>());
std::string compressed1 = compressor.compressFile(originalData);
std::cout << "压缩结果: " << compressed1 << std::endl;
std::string decompressed1 = compressor.decompressFile(compressed1);
std::cout << "解压结果: " << decompressed1 << std::endl;
// 切换到RAR策略
std::cout << "\n--- 切换策略 ---" << std::endl;
compressor.setStrategy(std::make_unique<RarCompression>());
std::string compressed2 = compressor.compressFile(originalData);
std::cout << "压缩结果: " << compressed2 << std::endl;
std::string decompressed2 = compressor.decompressFile(compressed2);
std::cout << "解压结果: " << decompressed2 << std::endl;
return 0;
}🔧 接口隔离原则
细粒度接口设计
cpp
#include <iostream>
#include <string>
// 好的设计:接口隔离
class Readable {
public:
virtual ~Readable() = default;
virtual std::string read() = 0;
};
class Writable {
public:
virtual ~Writable() = default;
virtual void write(const std::string& data) = 0;
};
class Seekable {
public:
virtual ~Seekable() = default;
virtual void seek(int position) = 0;
virtual int tell() const = 0;
};
// 只读文件:只实现需要的接口
class ReadOnlyFile : public Readable {
private:
std::string filename_;
std::string content_;
public:
ReadOnlyFile(const std::string& filename, const std::string& content)
: filename_(filename), content_(content) {}
std::string read() override {
std::cout << "读取文件: " << filename_ << std::endl;
return content_;
}
};
// 可读写文件:实现多个接口
class ReadWriteFile : public Readable, public Writable, public Seekable {
private:
std::string filename_;
std::string content_;
int position_;
public:
ReadWriteFile(const std::string& filename)
: filename_(filename), position_(0) {}
std::string read() override {
std::cout << "读取文件: " << filename_ << std::endl;
return content_;
}
void write(const std::string& data) override {
content_ = data;
std::cout << "写入文件: " << filename_ << std::endl;
}
void seek(int position) override {
position_ = position;
std::cout << "定位到位置: " << position << std::endl;
}
int tell() const override {
return position_;
}
};
// 网络流:只支持读写,不支持定位
class NetworkStream : public Readable, public Writable {
private:
std::string url_;
public:
NetworkStream(const std::string& url) : url_(url) {}
std::string read() override {
std::cout << "从网络读取: " << url_ << std::endl;
return "网络数据";
}
void write(const std::string& data) override {
std::cout << "写入网络: " << url_ << " 数据: " << data << std::endl;
}
};
// 使用接口的函数
void processReadable(Readable& readable) {
std::cout << "处理可读对象: " << readable.read() << std::endl;
}
void processWritable(Writable& writable) {
writable.write("测试数据");
}
int main() {
std::cout << "=== 接口隔离原则 ===" << std::endl;
ReadOnlyFile readFile("readme.txt", "这是只读文件内容");
ReadWriteFile rwFile("data.txt");
NetworkStream netStream("http://example.com");
// 通过Readable接口访问
processReadable(readFile);
processReadable(rwFile);
processReadable(netStream);
std::cout << "\n--- 写入操作 ---" << std::endl;
// 通过Writable接口访问
processWritable(rwFile);
processWritable(netStream);
// processWritable(readFile); // 编译错误,只读文件不支持写入
std::cout << "\n--- 定位操作 ---" << std::endl;
// 只有支持Seekable的对象才能定位
Seekable* seekable = &rwFile;
seekable->seek(100);
std::cout << "当前位置: " << seekable->tell() << std::endl;
return 0;
}📋 接口最佳实践
契约式编程
cpp
#include <iostream>
#include <stdexcept>
#include <cassert>
// 定义明确契约的接口
class Stack {
public:
virtual ~Stack() = default;
// 前置条件:无
// 后置条件:栈中增加一个元素
virtual void push(int value) = 0;
// 前置条件:栈非空
// 后置条件:移除栈顶元素
virtual void pop() = 0;
// 前置条件:栈非空
// 后置条件:返回栈顶元素,栈内容不变
virtual int top() const = 0;
// 前置条件:无
// 后置条件:返回栈是否为空
virtual bool empty() const = 0;
// 前置条件:无
// 后置条件:返回栈大小
virtual size_t size() const = 0;
};
class ArrayStack : public Stack {
private:
static const size_t MAX_SIZE = 100;
int data_[MAX_SIZE];
size_t size_;
public:
ArrayStack() : size_(0) {}
void push(int value) override {
// 检查前置条件
if (size_ >= MAX_SIZE) {
throw std::overflow_error("栈已满");
}
data_[size_++] = value;
// 确保后置条件
assert(size_ > 0);
assert(data_[size_ - 1] == value);
}
void pop() override {
// 检查前置条件
if (empty()) {
throw std::underflow_error("栈为空,无法弹出");
}
size_t oldSize = size_;
--size_;
// 确保后置条件
assert(size_ == oldSize - 1);
}
int top() const override {
// 检查前置条件
if (empty()) {
throw std::underflow_error("栈为空,无法获取顶部元素");
}
return data_[size_ - 1];
}
bool empty() const override {
return size_ == 0;
}
size_t size() const override {
return size_;
}
};
// 测试契约的函数
void testStack(Stack& stack) {
std::cout << "测试栈接口契约..." << std::endl;
// 测试空栈
assert(stack.empty());
assert(stack.size() == 0);
// 测试推入元素
stack.push(10);
assert(!stack.empty());
assert(stack.size() == 1);
assert(stack.top() == 10);
stack.push(20);
assert(stack.size() == 2);
assert(stack.top() == 20);
// 测试弹出元素
stack.pop();
assert(stack.size() == 1);
assert(stack.top() == 10);
stack.pop();
assert(stack.empty());
assert(stack.size() == 0);
std::cout << "所有契约测试通过!" << std::endl;
}
int main() {
std::cout << "=== 契约式编程 ===" << std::endl;
ArrayStack stack;
try {
testStack(stack);
// 演示错误处理
std::cout << "\n--- 测试错误条件 ---" << std::endl;
try {
stack.pop(); // 空栈弹出
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
try {
stack.top(); // 空栈获取顶部
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
} catch (const std::exception& e) {
std::cout << "测试失败: " << e.what() << std::endl;
}
return 0;
}总结
接口是C++面向对象设计的重要工具,提供了清晰的契约规范:
接口特点
- 纯虚函数:定义必须实现的方法
- 无实现细节:只定义方法签名
- 多重继承:类可以实现多个接口
- 契约规范:明确前置和后置条件
设计原则
| 原则 | 说明 | 优势 |
|---|---|---|
| 接口隔离 | 接口应该小而专一 | 降低耦合度 |
| 依赖倒置 | 依赖抽象而非具体 | 提高灵活性 |
| 契约编程 | 明确前后置条件 | 保证正确性 |
| 单一职责 | 每个接口只负责一个职责 | 易于理解和维护 |
最佳实践
- 保持接口简洁和稳定
- 使用纯虚析构函数
- 避免接口过于庞大
- 明确定义方法契约
- 优先组合多个小接口
接口设计是构建可扩展、可测试和可维护软件系统的关键技术。