方法 1:前向声明+指针
-
使用前向声明:在类的头文件中前向声明另一个类,避免直接包含头文件。
-
使用指针或引用:在类的成员变量或方法参数中使用对方类的指针或引用,因为前向声明仅支持指针/引用类型。
-
分离头文件与实现:在源文件(.cpp)中包含对方的头文件,确保方法实现时能访问完整定义。
示例代码:
A.h(类A的头文件)
#ifndef A_H
#define A_H
// 前向声明类B
class B;
class A {
private:
B* b_ptr; // 使用指针
public:
A(B* b);
void methodA();
void setB(B* b);
};
#endif
B.h(类B的头文件)
#ifndef B_H
#define B_H
// 前向声明类A
class A;
class B {
private:
A* a_ptr; // 使用指针
public:
B(A* a);
void methodB();
void setA(A* a);
};
#endif
A.cpp(类A的实现)
#include "A.h"
#include "B.h" // 包含B的头文件以访问完整定义
A::A(B* b) : b_ptr(b) {}
void A::methodA() {
// 调用B的方法
b_ptr->methodB();
}
void A::setB(B* b) {
b_ptr = b;
}
B.cpp(类B的实现)
#include "B.h"
#include "A.h" // 包含A的头文件以访问完整定义
B::B(A* a) : a_ptr(a) {}
void B::methodB() {
// 调用A的方法
a_ptr->methodA();
}
void B::setA(A* a) {
a_ptr = a;
}
main.cpp(使用示例)
#include "A.h"
#include "B.h"
int main() {
B b(nullptr);
A a(&b);
b.setA(&a);
a.methodA(); // 触发相互调用
return 0;
}
关键点:
-
避免头文件循环包含:通过前向声明替代直接包含头文件。
-
指针/引用替代对象:成员变量或参数中使用指针/引用,避免编译器需要完整定义。
-
实现分离:在源文件中包含必要头文件,确保方法实现时可访问类成员。
注意事项:
-
递归调用风险:确保逻辑正确,避免无限递归(如
a.methodA()调用b.methodB(),后者又调用a.methodA())。 -
内存管理:若使用原始指针,需注意对象生命周期,避免悬挂指针。建议使用智能指针(如
std::shared_ptr)增强安全性。
方法 2:使用接口(抽象基类)
通过定义接口类(抽象基类)解耦具体实现,让相互调用的类依赖接口而非具体类型。
示例代码:
// IInterface.h
#pragma once
class IInterface {
public:
virtual void doSomething() = 0;
virtual ~IInterface() = default;
};
// A.h
#pragma once
#include "IInterface.h"
class A : public IInterface {
public:
void doSomething() override;
void setDependency(IInterface* dep);
private:
IInterface* dependency = nullptr;
};
// B.h
#pragma once
#include "IInterface.h"
class B : public IInterface {
public:
void doSomething() override;
void setDependency(IInterface* dep);
private:
IInterface* dependency = nullptr;
};
// A.cpp
#include "A.h"
void A::doSomething() {
if (dependency) dependency->doSomething();
}
void A::setDependency(IInterface* dep) { dependency = dep; }
// B.cpp
#include "B.h"
void B::doSomething() {
if (dependency) dependency->doSomething();
}
void B::setDependency(IInterface* dep) { dependency = dep; }
// main.cpp
#include "A.h"
#include "B.h"
int main() {
A a;
B b;
a.setDependency(&b);
b.setDependency(&a);
a.doSomething(); // 通过接口调用
return 0;
}
优点:
-
完全解耦具体实现,符合面向接口编程原则。
-
支持多态和动态替换依赖对象。
缺点:
-
需要定义额外的接口类,代码量增加。
方法 3:使用中介者模式(Mediator Pattern)
通过引入一个中间协调类(Mediator),让原本直接交互的类通过中介者通信。
示例代码:
// Mediator.h
#pragma once
#include <memory>
class A;
class B;
class Mediator {
public:
virtual void notifyA() = 0;
virtual void notifyB() = 0;
virtual ~Mediator() = default;
};
// A.h
#pragma once
#include "Mediator.h"
class A {
public:
A(Mediator* mediator) : mediator_(mediator) {}
void callB() { mediator_->notifyB(); }
private:
Mediator* mediator_;
};
// B.h
#pragma once
#include "Mediator.h"
class B {
public:
B(Mediator* mediator) : mediator_(mediator) {}
void callA() { mediator_->notifyA(); }
private:
Mediator* mediator_;
};
// ConcreteMediator.cpp
#include "Mediator.h"
#include "A.h"
#include "B.h"
class ConcreteMediator : public Mediator {
public:
ConcreteMediator(A* a, B* b) : a_(a), b_(b) {}
void notifyA() override { /* 处理 A 的通知 */ }
void notifyB() override { /* 处理 B 的通知 */ }
private:
A* a_;
B* b_;
};
// main.cpp
int main() {
ConcreteMediator mediator;
A a(&mediator);
B b(&mediator);
a.callB(); // 通过中介者调用
return 0;
}
优点:
-
集中控制逻辑,减少类间的直接依赖。
-
扩展性强,新增交互逻辑只需修改中介者。
缺点:
-
需要额外设计中介者类,可能引入复杂度。
方法 4:依赖注入(Dependency Injection)
通过外部容器管理依赖关系,动态注入对象实例,避免硬编码依赖。
示例代码(使用简单的手动依赖注入):
// ServiceA.h
#pragma once
class ServiceB;
class ServiceA {
public:
ServiceA(ServiceB* b) : b_(b) {}
void doSomething();
private:
ServiceB* b_;
};
// ServiceB.h
#pragma once
class ServiceA;
class ServiceB {
public:
ServiceB(ServiceA* a) : a_(a) {}
void doSomething();
private:
ServiceA* a_;
};
// main.cpp
#include "ServiceA.h"
#include "ServiceB.h"
int main() {
ServiceA* a = nullptr;
ServiceB* b = nullptr;
// 手动创建并注入依赖
a = new ServiceA(b);
b = new ServiceB(a);
a->doSomething();
delete a;
delete b;
return 0;
}
优点:
-
依赖关系清晰,易于单元测试(通过 Mock 对象)。
-
符合单一职责原则。
缺点:
-
需要手动管理依赖,复杂项目可借助框架(如 Google Fruit、Boost.DI)。
方法 5:模板方法(Template-based)
利用模板在编译时解决依赖,适用于类型已知的场景。
示例代码:
// A.h
#pragma once
template <typename T>
class A {
public:
void callDependency(T& dep) { dep.method(); }
};
// B.h
#pragma once
template <typename T>
class B {
public:
void callDependency(T& dep) { dep.method(); }
};
// main.cpp
#include "A.h"
#include "B.h"
int main() {
A<B<int>> a;
B<A<int>> b;
// 使用时需确保类型兼容
return 0;
}
优点:
-
编译时解决依赖,无运行时开销。
-
灵活性强,适合泛型编程。
缺点:
-
模板代码可能难以调试。
-
类型约束需明确,否则易导致编译错误。
方法 6:观察者模式(Observer Pattern)
适用于事件驱动的场景,通过订阅-通知机制实现间接调用。
示例代码:
// Observer.h
#pragma once
#include <functional>
class Observer {
public:
virtual void onEvent() = 0;
virtual ~Observer() = default;
};
// Subject.h
#pragma once
#include <vector>
#include "Observer.h"
class Subject {
public:
void addObserver(Observer* obs) { observers_.push_back(obs); }
void notify() { for (auto obs : observers_) obs->onEvent(); }
private:
std::vector<Observer*> observers_;
};
// A.h
#pragma once
#include "Observer.h"
class A : public Observer {
public:
void onEvent() override { /* 处理事件 */ }
};
// B.h
#pragma once
#include "Observer.h"
class B : public Observer {
public:
void onEvent() override { /* 处理事件 */ }
};
优点:
-
松耦合,动态管理依赖关系。
-
支持一对多通知。
缺点:
-
需要定义事件机制,适合异步场景。
总结
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 前向声明+指针 | 简单相互调用 | 直接、无需额外设计 | 需管理指针,可能循环调用 |
| 接口抽象 | 需要多态或替换实现 | 高度解耦,符合 OCP 原则 | 需定义接口类 |
| 中介者模式 | 复杂交互逻辑 | 集中控制,减少直接依赖 | 引入额外中介者类 |
| 依赖注入 | 需要灵活管理依赖 | 易于测试,依赖清晰 | 手动注入繁琐,框架有学习成本 |
| 模板方法 | 编译时确定类型 | 零运行时开销,泛型友好 | 模板复杂度高,调试困难 |
| 观察者模式 | 事件驱动或异步场景 | 松耦合,支持动态通知 | 需事件机制,可能过度设计 |
选择方法时,需权衡 代码复杂度、维护成本 和 具体需求。对于简单项目,前向声明+指针足够;对于大型项目,接口抽象或依赖注入更合适。
1万+

被折叠的 条评论
为什么被折叠?



