第31章 可重用代码
代码重用的设计原则
1. 单一职责原则
核心概念:一个模块或类应该只负责一项功能,避免职责混合导致的耦合和维护困难。
实践指南:
- 每个类或函数应该有明确的责任边界
- 当一个类的职责开始膨胀时,考虑拆分为多个专注的类
- 使用接口分离原则(ISP)确保客户端只依赖于它们实际使用的接口
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class UserManager { public: void createUser(const std::string& name); void deleteUser(int userId); void sendEmail(const std::string& email, const std::string& message); void generateReport(); };
class UserManager { public: void createUser(const std::string& name); void deleteUser(int userId); };
class EmailService { public: void sendEmail(const std::string& email, const std::string& message); };
class ReportGenerator { public: void generateReport(); };
|
2. 开放封闭原则
核心概念:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
实践指南:
- 使用抽象基类定义接口
- 通过继承和多态实现扩展
- 避免在已有代码中添加条件分支来处理新功能
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Shape { public: virtual ~Shape() = default; virtual double area() const = 0; };
class Circle : public Shape { public: explicit Circle(double radius) : radius_(radius) {} double area() const override { return M_PI * radius_ * radius_; } private: double radius_; };
class Rectangle : public Shape { public: Rectangle(double width, double height) : width_(width), height_(height) {} double area() const override { return width_ * height_; } private: double width_; double height_; };
class AreaCalculator { public: double calculateTotalArea(const std::vector<std::unique_ptr<Shape>>& shapes) { double total = 0.0; for (const auto& shape : shapes) { total += shape->area(); } return total; } };
|
3. 依赖倒置原则
核心概念:高层模块不应该依赖低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
实践指南:
- 使用接口或抽象类定义依赖关系
- 通过构造函数注入或 setter 注入实现依赖注入
- 避免硬编码依赖关系
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class Database { public: virtual ~Database() = default; virtual void connect() = 0; virtual void disconnect() = 0; virtual void execute(const std::string& query) = 0; };
class MySQLDatabase : public Database { public: void connect() override { } void disconnect() override { } void execute(const std::string& query) override { } };
class PostgreSQLDatabase : public Database { public: void connect() override { } void disconnect() override { } void execute(const std::string& query) override { } };
class UserRepository { public: explicit UserRepository(std::unique_ptr<Database> db) : db_(std::move(db)) { db_->connect(); } void saveUser(const std::string& name) { db_->execute("INSERT INTO users (name) VALUES ('" + name + "')"); } private: std::unique_ptr<Database> db_; };
|
模块化编程
1. 模块设计策略
核心概念:将系统分解为独立的、可管理的模块,每个模块包含相关的功能和数据。
设计原则:
- 内聚性:模块内部的元素应该高度相关
- 耦合性:模块之间的依赖应该最小化
- 接口清晰:每个模块应该有明确的接口,隐藏内部实现细节
模块划分策略:
- 按功能划分:将相关功能组织到同一个模块
- 按层次划分:将系统分为表示层、业务逻辑层和数据访问层
- 按服务划分:将系统分解为独立的服务,每个服务提供特定的功能
2. 命名空间管理
核心概念:使用命名空间避免名称冲突,组织代码结构。
最佳实践:
- 为每个模块创建独立的命名空间
- 使用嵌套命名空间表示模块层次结构
- 避免使用
using namespace std; 等全局使用声明
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| namespace company { namespace project { namespace utils { class StringHelper { public: static std::string trim(const std::string& str); }; } namespace data { class DatabaseConnection { public: void connect(); }; } } }
int main() { std::string s = " hello world "; std::string trimmed = company::project::utils::StringHelper::trim(s); company::project::data::DatabaseConnection db; db.connect(); return 0; }
|
3. 头文件组织
核心概念:合理组织头文件,减少编译依赖,提高编译速度。
最佳实践:
- 使用前置声明减少头文件包含
- 实现头文件保护(pragma once 或 include guards)
- 遵循头文件包含顺序:C 标准库、C++ 标准库、第三方库、本地头文件
- 避免在头文件中定义变量或函数实现
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #pragma once
class ForwardDeclaredClass;
class MyClass { public: void doSomething(ForwardDeclaredClass* obj); private: int value_; };
#include "header_guard.h" #include "forward_declared_class.h"
void MyClass::doSomething(ForwardDeclaredClass* obj) { }
|
接口设计
1. 接口设计原则
核心概念:设计清晰、稳定、易用的接口,减少客户端代码的理解和使用成本。
设计原则:
- 最小接口原则:接口应该只包含客户端真正需要的方法
- 接口稳定性:接口一旦发布,应该保持稳定,避免频繁变更
- 接口一致性:相似功能的接口应该有一致的命名和参数设计
- 错误处理:明确接口的错误处理策略,如异常、返回值或错误码
2. 接口实现技术
核心概念:使用不同的技术实现接口,满足不同的设计需求。
实现技术:
2.1 抽象基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Logger { public: virtual ~Logger() = default; virtual void log(const std::string& message) = 0; virtual void logError(const std::string& message) = 0; };
class ConsoleLogger : public Logger { public: void log(const std::string& message) override { std::cout << "INFO: " << message << std::endl; } void logError(const std::string& message) override { std::cerr << "ERROR: " << message << std::endl; } };
|
2.2 策略模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class SortingStrategy { public: virtual ~SortingStrategy() = default; virtual void sort(std::vector<int>& data) = 0; };
class BubbleSort : public SortingStrategy { public: void sort(std::vector<int>& data) override { } };
class QuickSort : public SortingStrategy { public: void sort(std::vector<int>& data) override { } };
class Sorter { public: void setStrategy(std::unique_ptr<SortingStrategy> strategy) { strategy_ = std::move(strategy); } void sortData(std::vector<int>& data) { if (strategy_) { strategy_->sort(data); } } private: std::unique_ptr<SortingStrategy> strategy_; };
|
2.3 模板接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| template<typename T> class Repository { public: virtual ~Repository() = default; virtual void save(const T& item) = 0; virtual std::optional<T> findById(int id) = 0; virtual std::vector<T> findAll() = 0; };
template<typename T> class InMemoryRepository : public Repository<T> { public: void save(const T& item) override { items_.push_back(item); } std::optional<T> findById(int id) override { return std::nullopt; } std::vector<T> findAll() override { return items_; } private: std::vector<T> items_; };
|
依赖管理
1. 依赖注入
核心概念:将对象的依赖关系从对象内部移到外部,由外部容器或客户端负责创建和注入依赖。
注入方式:
- 构造函数注入:通过构造函数参数注入依赖
- Setter 注入:通过 setter 方法注入依赖
- 接口注入:通过实现特定接口注入依赖
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class ConfigService { public: virtual std::string getConfig(const std::string& key) = 0; };
class FileConfigService : public ConfigService { public: std::string getConfig(const std::string& key) override { return "value"; } };
class DatabaseService { public: explicit DatabaseService(std::shared_ptr<ConfigService> config) : config_(config) { connectionString_ = config_->getConfig("database.connection"); } void connect() { } private: std::shared_ptr<ConfigService> config_; std::string connectionString_; };
int main() { auto config = std::make_shared<FileConfigService>(); DatabaseService db(config); db.connect(); return 0; }
|
2. 依赖管理工具
核心概念:使用依赖管理工具管理项目依赖,确保依赖的一致性和可重复性。
常用工具:
- CMake:跨平台构建系统,支持依赖管理
- Conan:C++ 包管理器
- vcpkg:Microsoft 开发的 C++ 包管理器
- Meson:现代构建系统,支持依赖管理
CMake 依赖管理示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| cmake_minimum_required(VERSION 3.14) project(MyProject)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system) find_package(OpenSSL REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system OpenSSL::SSL OpenSSL::Crypto )
|
3. 依赖隔离
核心概念:隔离模块之间的依赖,减少依赖传播,提高系统的可维护性和可测试性。
实现策略:
- 依赖倒置:通过抽象接口隔离具体实现
- 适配器模式:使用适配器转换不同接口之间的差异
- 门面模式:为子系统提供统一的接口,减少客户端与子系统之间的依赖
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class ThirdPartyLogger { public: void logMessage(int severity, const char* message); };
class LoggerAdapter : public Logger { public: explicit LoggerAdapter(ThirdPartyLogger* logger) : logger_(logger) {} void log(const std::string& message) override { logger_->logMessage(1, message.c_str()); } void logError(const std::string& message) override { logger_->logMessage(0, message.c_str()); } private: ThirdPartyLogger* logger_; };
int main() { ThirdPartyLogger thirdPartyLogger; LoggerAdapter logger(&thirdPartyLogger); logger.log("Information message"); logger.logError("Error message"); return 0; }
|
代码组织
1. 目录结构
核心概念:设计合理的目录结构,组织源代码、头文件、测试和文档。
推荐结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| project/ ├── CMakeLists.txt # 主构建文件 ├── include/ # 公共头文件 │ └── project/ # 命名空间对应的目录 │ ├── module1/ # 模块1的头文件 │ └── module2/ # 模块2的头文件 ├── src/ # 源代码 │ ├── module1/ # 模块1的实现 │ └── module2/ # 模块2的实现 ├── tests/ # 测试代码 │ ├── unit/ # 单元测试 │ └── integration/ # 集成测试 ├── examples/ # 示例代码 ├── docs/ # 文档 └── external/ # 第三方依赖
|
2. 构建系统集成
核心概念:使用现代构建系统管理项目构建过程,支持跨平台构建和依赖管理。
CMake 最佳实践:
- 使用现代 CMake (3.14+) 语法
- 使用
target_ 系列命令管理目标属性 - 模块化 CMake 代码,使用函数和宏
- 支持不同的构建类型(Debug、Release、RelWithDebInfo)
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| cmake_minimum_required(VERSION 3.14) project(MyProject VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)
include(GNUInstallDirs)
add_library(myproject src/module1/module1.cpp src/module2/module2.cpp )
target_include_directories(myproject PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> PRIVATE src/ )
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE myproject)
install(TARGETS myproject myapp EXPORT MyProjectTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} )
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
测试和文档
1. 测试策略
核心概念:为可重用代码编写全面的测试,确保代码质量和可靠性。
测试层次:
- 单元测试:测试单个函数或类的行为
- 集成测试:测试多个组件之间的交互
- 系统测试:测试整个系统的行为
测试框架:
- Google Test:功能强大的 C++ 测试框架
- Catch2:现代 C++ 测试框架,支持 BDD 风格
- Boost.Test:Boost 库中的测试框架
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <gtest/gtest.h> #include "project/utils/string_helper.h"
TEST(StringHelperTest, Trim) { std::string input = " hello world "; std::string expected = "hello world"; EXPECT_EQ(company::project::utils::StringHelper::trim(input), expected); }
TEST(StringHelperTest, TrimEmpty) { std::string input = ""; std::string expected = ""; EXPECT_EQ(company::project::utils::StringHelper::trim(input), expected); }
TEST(StringHelperTest, TrimOnlySpaces) { std::string input = " "; std::string expected = ""; EXPECT_EQ(company::project::utils::StringHelper::trim(input), expected); }
|
2. 文档生成
核心概念:为可重用代码编写清晰、全面的文档,帮助其他开发者理解和使用代码。
文档工具:
- Doxygen:生成代码文档的标准工具
- Sphinx:生成项目文档,支持多种格式
- Markdown:编写简单的文档和 README 文件
Doxygen 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
namespace company::project::utils { class StringHelper { public:
static std::string trim(const std::string& str);
static std::vector<std::string> split(const std::string& str, char delimiter); }; }
|
实际应用案例
1. 可重用组件库
核心概念:创建一个包含常用功能的组件库,供多个项目复用。
组件库结构:
- 基础工具:字符串处理、日期时间、文件操作等
- 数据结构:自定义容器、算法等
- 网络通信:HTTP 客户端、WebSocket 等
- 配置管理:配置文件解析、环境变量处理等
- 日志系统:多级别日志、多种输出目标等
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| namespace company::common { class Logger { public: enum class Level { DEBUG, INFO, WARN, ERROR, FATAL }; virtual ~Logger() = default; virtual void log(Level level, const std::string& message) = 0; static std::unique_ptr<Logger> create(); }; class Config { public: virtual ~Config() = default; virtual std::string getString(const std::string& key) = 0; virtual int getInt(const std::string& key) = 0; virtual bool getBool(const std::string& key) = 0; static std::unique_ptr<Config> load(const std::string& path); }; class HttpClient { public: virtual ~HttpClient() = default; virtual std::string get(const std::string& url) = 0; virtual std::string post(const std::string& url, const std::string& data) = 0; static std::unique_ptr<HttpClient> create(); }; }
|
2. 插件系统
核心概念:设计一个插件系统,允许动态加载和卸载功能模块,提高系统的可扩展性。
实现技术:
- 使用动态链接库(DLL/SO)实现插件
- 定义插件接口和加载机制
- 使用工厂模式创建插件实例
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| class Plugin { public: virtual ~Plugin() = default; virtual std::string name() const = 0; virtual void initialize() = 0; virtual void shutdown() = 0; };
class PluginManager { public: void loadPlugin(const std::string& path) { void* handle = dlopen(path.c_str(), RTLD_LAZY); if (!handle) { throw std::runtime_error(dlerror()); } using CreatePluginFunc = Plugin* (*)(); CreatePluginFunc createPlugin = reinterpret_cast<CreatePluginFunc>(dlsym(handle, "createPlugin")); if (!createPlugin) { dlclose(handle); throw std::runtime_error("Invalid plugin: no createPlugin function"); } Plugin* plugin = createPlugin(); plugin->initialize(); plugins_.emplace_back(plugin); handles_.emplace_back(handle); } void unloadAllPlugins() { for (auto plugin : plugins_) { plugin->shutdown(); delete plugin; } for (auto handle : handles_) { dlclose(handle); } plugins_.clear(); handles_.clear(); } private: std::vector<Plugin*> plugins_; std::vector<void*> handles_; };
extern "C" { Plugin* createPlugin() { return new MyPlugin(); } }
class MyPlugin : public Plugin { public: std::string name() const override { return "MyPlugin"; } void initialize() override { std::cout << "MyPlugin initialized" << std::endl; } void shutdown() override { std::cout << "MyPlugin shutdown" << std::endl; } };
|
3. 服务框架
核心概念:设计一个服务框架,提供服务注册、发现、调用等功能,支持微服务架构。
框架组件:
- 服务注册中心:管理服务实例的注册和发现
- 服务代理:处理服务调用的负载均衡和故障转移
- 配置中心:管理服务配置的集中存储和动态更新
- 监控系统:收集服务运行状态和性能指标
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| class Service { public: virtual ~Service() = default; virtual std::string getName() const = 0; virtual void start() = 0; virtual void stop() = 0; };
class ServiceManager { public: void registerService(std::unique_ptr<Service> service) { services_[service->getName()] = std::move(service); } void startAll() { for (auto& [name, service] : services_) { std::cout << "Starting service: " << name << std::endl; service->start(); } } void stopAll() { for (auto& [name, service] : services_) { std::cout << "Stopping service: " << name << std::endl; service->stop(); } } Service* getService(const std::string& name) { auto it = services_.find(name); return it != services_.end() ? it->second.get() : nullptr; } private: std::unordered_map<std::string, std::unique_ptr<Service>> services_; };
class UserService : public Service { public: std::string getName() const override { return "UserService"; } void start() override { } void stop() override { } void createUser(const std::string& name) { } };
class OrderService : public Service { public: std::string getName() const override { return "OrderService"; } void start() override { } void stop() override { } void createOrder(int userId, double amount) { } };
|
性能优化
1. 编译时间优化
核心概念:减少代码的编译时间,提高开发效率。
优化策略:
- 使用前置声明减少头文件包含
- 合理使用 PCH (Precompiled Headers)
- 模块化代码,减少模块间的依赖
- 使用增量编译和并行编译
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ForwardDeclaredClass;
class MyClass { public: void method(ForwardDeclaredClass* obj); };
#include "forward_declared_class.h"
void MyClass::method(ForwardDeclaredClass* obj) { }
|
2. 运行时性能优化
核心概念:优化可重用代码的运行时性能,确保在各种场景下都能高效运行。
优化策略:
- 内存管理:减少内存分配和拷贝,使用移动语义
- 算法选择:根据数据规模选择合适的算法
- 缓存优化:提高缓存命中率,减少缓存 misses
- 并发处理:合理使用多线程,提高并行性能
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Data { public: Data() = default; Data(Data&& other) noexcept : data_(std::move(other.data_)) { other.data_.clear(); } Data& operator=(Data&& other) noexcept { if (this != &other) { data_ = std::move(other.data_); other.data_.clear(); } return *this; } void add(const std::string& value) { data_.push_back(value); } private: std::vector<std::string> data_; };
void processArray(std::vector<int>& data) { for (size_t i = 0; i < data.size(); ++i) { data[i] = data[i] * 2 + 1; } }
|
最佳实践总结
1. 设计原则
- 优先使用组合而非继承:减少耦合,提高灵活性
- 接口优于实现:依赖抽象,而非具体实现
- 保持简单:简洁的代码更易于理解和维护
- 防御性编程:处理边界情况,验证输入参数
- 代码一致性:遵循一致的编码风格和命名约定
2. 实现技巧
- 使用现代 C++ 特性:智能指针、移动语义、lambda 表达式等
- 合理使用模板:提高代码重用性,但避免过度使用导致编译时间过长
- 异常安全:确保代码在异常情况下能够保持一致的状态
- 资源管理:使用 RAII 原则管理资源,避免资源泄漏
- 测试驱动开发:先写测试,再实现功能,确保代码质量
3. 项目管理
- 版本控制:使用 Git 等版本控制系统管理代码
- 持续集成:自动构建、测试和部署
- 代码审查:定期进行代码审查,提高代码质量
- 文档更新:及时更新文档,保持与代码同步
- 依赖管理:使用包管理器管理第三方依赖
4. 团队协作
- 代码规范:制定并遵循团队代码规范
- 知识共享:定期分享技术知识和最佳实践
- 代码复用:鼓励团队成员共享和复用代码
- 问题追踪:使用 issue 追踪系统管理 bug 和功能请求
- 代码所有权:明确代码责任,确保代码质量
总结
可重用代码是提高开发效率、减少维护成本、保证代码质量的关键。通过遵循良好的设计原则、采用模块化的编程方式、设计清晰的接口、合理管理依赖关系,我们可以创建出高质量的可重用代码。
在实际项目中,我们应该:
- 从设计开始:在编写代码之前,先考虑代码的可重用性
- 持续改进:定期重构代码,提高其可重用性和可维护性
- 测试验证:为可重用代码编写全面的测试,确保其可靠性
- 文档完善:为可重用代码提供清晰的文档,方便其他开发者使用
- 共享推广:在团队内部或开源社区共享可重用代码,最大化其价值
通过不断实践和总结,我们可以构建一个强大的可重用代码库,为项目开发提供有力的支持,同时也为整个 C++ 社区做出贡献。