详解C/C++中低耦合代码的设计实现
在C/C++开发中,模块间相互依赖是引发代码僵化和维护困难的常见原因。本文针对类间环依赖、函数调用耦合等典型场景,提供从基础修复到高级架构优化的完整解决方案。
消除类间环依赖的方法
在设计面向对象系统时,有时需要两个类或模块相互“认识”,例如网店系统中的客户类(Customer)与账户类(Account)。这种直接的互相引用会导致UML中的环依赖问题:
开发者常采用结构体或类前置声明来解决编译错误。
customer.h
#ifndef CUSTOMER_H_
#define CUSTOMER_H_
class Account;
class Customer
{
// ...
void setAccount(Account* account);
{
customerAccount = account;
}
//...
private:
Account* customerAccount;
};
#endif
account.h
#ifndef ACCOUNT_H_
#define ACCOUNT_H_
class Customer;
class Account
{
public:
//...
void setOwer(Customer* customer)
{
ower = customer;
}
//...
private:
Customer* ower;
}
#endif
虽然上述方式能消除编译器的报错,但这种方案并不完美。以下调用示例展示了其致命隐患:
#include "account.h"
#include "customer.h"
//...
Account *account = new Accout{};
Customer *customer = new Customer{};
account->setOwer(customer);
customer->setAccount(account);
当删除 Account 实例时,Customer 实例仍会存在,但其内部指向 Account 的指针变为悬垂指针(空指针或未初始化状态)。此时使用或解引用此指针将导致严重的问题,如段错误或数据损坏。
应用依赖倒置原则
依赖倒置原则(DIP)能有效解决此类强耦合问题。核心思想是不让两个具体类直接访问对方,而是只通过抽象接口进行交互。
第1步:定义抽象接口
首先提取公共接口。以从 Customer 中提取的 Owner 为例,在该接口中声明纯虚函数,强制子类必须覆盖它:
ower.h
#ifndef OWNER_H_
#define OWNER_H_
#include <memory>
#include <string>
class owner
{
public:
virtual ~owner()=default;
virtual std::string getName() const = 0;
};
using OwnerPtr = std::shared_ptr<Owner>;
#endif
第2步:实现业务类继承接口
Customer.h
#ifndef CUSTOMER_H_
#define CUSTOMER_H_
#include "Owner.h"
#include "Account.h"
class Customer:public Owner
{
public:
void setAccount(AccountPtr account)
{
customerAccount = account;
}
virtual std::string getName() const override{
//return string
}
private:
Account customerAccount;
};
using CustomerPtr = std::shared_ptr<Customer>;
#endif
第3步:重构类之间的关联
account.h
#ifndef ACCOUNT_H_
#define ACCOUNT_H_
#include "Owner.h"
class Account
{
public:
void setOwner(OwnerPtr owner)
{
this->owner = owner;
}
private:
OwnerPtr owner;
};
using AccountPtr = std::shared_ptr<Account>;
经过上述重构后,Customer 和 Account 之间消除了直接的环依赖,它们仅依赖于抽象接口 Owner,从而实现了松耦合设计。
降低功能耦合性:回调函数与信号槽
对于非面向对象的功能模块,我们通常采用回调函数或者信号槽的方式来联系两个模块,以降低功能代码的耦合度。
使用回调函数解耦
通过函数指针,可以将功能的执行权交给调用者,被调模块只需负责逻辑处理,而不需关心调用方的具体实现。
a.h (功能模块A声明)
#pragma once
#ifndef A_H_
#define A_H_
#include <stdio.h>
int test(int c);
#endif
a.c (功能模块A实现)
#include "a.h"
int test(int c)
{
c = 0x11112;
return c;
}
b.h (调用模块B声明)
#pragma once
#ifndef B_H_
#define B_H_
#include <stdio.h>
typedef int (*PtrFunA)(int);
int testb(PtrFunA, int c);
#endif
b.c (调用模块B实现)
#include "b.h"
int testb(PtrFunA a, int c)
{
int ret = a(c);
printf("%d\n", ret);
return ret;
}
main.c (入口文件)
#include <stdio.h>
#include "a.h"
#include "b.h"
int main()
{
testb(test,123);
}
这样做的好处是将A模块的功能代码完全封装在 a.c 中,B模块的功能代码只写在 b.c 中。为进一步增强通用性,改进后的回调函数可以使用 void* 进行传参,在 a.c 中对 void* 类型进行判断,这是许多底层库常用的操作模式。
信号槽机制
相比回调函数,信号槽机制更加灵活,代码的耦合度更低。但由于其实现较为复杂(通常涉及宏或特定的事件分发框架),在实际项目中建议优先使用成熟的第三方库或现代C++特性实现,此处暂不展开具体代码实现细节。
以上就是详解C/C++中低耦合代码的设计实现的详细内容,更多关于C++低耦合代码的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









