在软件设计过程中,到处充满着陷阱,程序结构设计的不合理,会让你花费成倍的时间在一个死胡同里苦苦的寻找出路。下面详细解释一下如何解决常见的类相互包含的解决方法,使得模块的完全独立。
假设有两个类classA 、classB。classA 中包含classB。例如
#include "classB.h"
#include <iostream>
using namespace std;
class classA
{
private:
classB cb;
public:
int show(int a);
};
int classA::show(int a)
{
cout << a << endl;
}
在classB中有一个函数需要获取classA的某属性a.例如
class classA; // 前置声明
class classB
{
private:
public:
void showA();
};
void classB:showA()
{
//调用classA中的show函数 (我们需要完成的功能)
}
如何实现这个功能呢
最简单的办法是在classB的头文件中添加一行前置声明,
class classA;
然后在classB的定义文件中添加一个包含头文件
include "classA.h"
这样classB便可以声明并且使用classA的对象。
这样看起来似乎能解决问题,但是不知不觉中就陷入了陷阱当中。因为我们一厢情愿的在classB中include了一个它的父类的头文件。
这实在不是一个明智之举,一旦classB需要做成一个单独的模块,如何解耦将会是一个问题。
由此分析可以看出,我们不应该在classB中去包含一个classA的头文件。
新的解决方法产生了,我们换一种思路,于是可以这样考虑。
在classB中提供一个函数接口,接收一个函数指针,而classA中有一个注册函数,这个注册函数会调用classB提供的这个接口,把
classA的一个函数地址注册到classB中去,这样在调用classB::showA()函数时,可以让它实际去执行这个函数指针所指向的
classA::show()函数。实现方法如下。
========================classB.h===============================
class classB
{
private:
int (classA::*ptr)(int); // 声明一个classA的成员函数指针 ,函数参数和返回值类型都为int
classA *ca;
public:
void showA();
// 提供注册的函数接口,由于类成员函数指针是相对于类的偏移量,所以必须传递一个类指针才能正确调用函数指针所指向的方法
void setPtr( classA *a, int (classA::*p) (int) );
};
void classB:showA()
{
//调用classA中的show函数 (我们需要完成的功能)
(ca->*p)(1); // 实现调用,注意前面一对括号不能少,否则会因为操作符的优先级导致链接错误
}
void classB::setPtr( classA *a, int (classA::*p)(int) )
{
ca = a;
ptr = p;
}
========================classA.h===============================
#include "classB.h"
#include <iostream>
using namespace std;
class classA
{
private:
classB cb;
public:
int show(int a);
void regist();
classB getB();
};
classB getB()
{
return cb;
}
int classA::show(int a)
{
cout << a << endl;
}
void classA::regist() // 注册函数
{
cb.setPtr(&classA::show); // 传入函数指针
}
========================main.cpp===============================
int main()
{
A a;
a.regist();
a.getB().showA(); // 于是当调用A.getB().showA()时,实际调用的是A中的show()成员函数。
}
当然到目前为止并没有解决问题,大家仔细看了就会发现这种方式仍然需要在classB中include "classA.h",因为
在classB中不仅需要声明classA的类型指针,还要声明classA的成员函数指针。那该怎么办呢?
template将会帮助我们解决这个问题!
我们可以用template 来替代classB中的classA类型。
对classB.h进行修改
======================修改后的classB.h===============================
template < class T>
class classB
{
private:
int ( T::*ptr)(int); // 声明一个classA的成员函数指针 ,函数参数和返回值类型都为int
T *ca;
public:
void showA();
// 提供注册的函数接口,由于类成员函数指针是相对于类的偏移量,所以必须传递一个类指针才能正确调用函数指针所指向的方法
void setPtr( T*a, int ( T::*p) (int) );
};
template < class T>
void classB<T>:showA()
{
//调用classA中的show函数 (我们需要完成的功能)
(ca->*p)(1); // 实现调用,注意前面一对括号不能少,否则会因为操作符的优先级导致链接错误
}
template < class T>
void classB<T>::setPtr( T*a, int (T::*p)(int) )
{
ca = a;
ptr = p;
}
========================修改后的classA.h===============================
#include "classB.h"
#include <iostream>
using namespace std;
class classA
{
private:
classB cb;
public:
int show(int a);
void regist();
classB<classA> getB();
};
classB<classA> getB()
{
return cb;
}
int classA::show(int a)
{
cout << a << endl;
}
void classA::regist() // 注册函数
{
cb.setPtr(&classA::show); // 传入函数指针
}
注意:因为模板不是函数,它们不能单独编译,模板必须与特定的模板实例化请求一起使用。最简单的方法是将所有模板信息放在一个头文件重,并在要使用这些模板的文件中包含该头文件。如果编译器实现了新的export关键字, 则可以将模板方法定义在一个独立的文件中,条件是每个模板声明都以export开始。
例如
export template <class T>
class classB
{
...
};
至此我们就完全可以把类classB完全分离出来,即使再复杂的逻辑我们也可以用类似的方法来解决。降低模块间的耦合度。设计健壮性强、易扩展的程序来。