Skip to content

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++面向对象设计的重要工具,提供了清晰的契约规范:

接口特点

  • 纯虚函数:定义必须实现的方法
  • 无实现细节:只定义方法签名
  • 多重继承:类可以实现多个接口
  • 契约规范:明确前置和后置条件

设计原则

原则说明优势
接口隔离接口应该小而专一降低耦合度
依赖倒置依赖抽象而非具体提高灵活性
契约编程明确前后置条件保证正确性
单一职责每个接口只负责一个职责易于理解和维护

最佳实践

  • 保持接口简洁和稳定
  • 使用纯虚析构函数
  • 避免接口过于庞大
  • 明确定义方法契约
  • 优先组合多个小接口

接口设计是构建可扩展、可测试和可维护软件系统的关键技术。

本站内容仅供学习和研究使用。