都在说解耦合,但是如何解,哪些情况下有偶尔呢?看看前人的总结总是好的
此处都是针对编译时的解偶尔,不涉及链接和最后发布。
1、编译时耦合
1.1 IsA的关系
继承:class D: public B
不管继承方式,B的变化都将影响D的重新编译。D的重新编译将会继续递归下去
1.2 Has A/holds A
D中包含b1,b2对象。b1,b2的变化会影响D的编译
1.3 内联函数
D内联函数的实现变化,都会影响包含接口的重新编译。内联函数尽量不要有函数调用,这样其实是有依赖关系下去的。
1.4私有成员/保护成员
与1.2有点类似,private变量的类型,增加,删除都会影响该类。函数参数,返回值也会影响其重新编译
1.5 包含指令
明确的include。不用include的头文件被包含,也会导致相应文件重新编译
1.6 默认参数
默认参数变化
1.7 枚举类型变化
宏定义,typedef,const全局值,枚举都会导致重新编译
所以所有错误码或则类型定义在一个头文件中,对于大型项目不是什么好方法
2、隔离技术。如何隔离,针对相应情况相应的处理,而不是完完全全按照下面方法,因为这些方法也有弊端
2.1 移除私有继承
因为是私有继承,目的一般是希望使用里面的部分方法作为辅助。
解决方案:通过代理模式即可,将继承的基类申明成成员类。然后子类提供部分访问函数,另外此处不要使用内联,否则就还是进入了耦合。失去了代理,前置申明解耦的意义了。另外写代码时候发现一个私有继承比较有意思的写法
基类
class CInsulateHelp
{
public:
CInsulateHelp();
~CInsulateHelp();
void f(int);
protected:
void f_protected(int);
private:
};
子类私有继承
class CInsulate: private CInsulateHelp
{
public:
CInsulate();
~CInsulate();
void Test();
// 该语句注释掉,f就是protected了,是无法被外部访问的了。
// 因为是private继承。函数也是成员对象!!!,也可以当变量使用
CInsulateHelp::f;
// 也可以把protected的成员申明成public.但是无法改变private类型,因为private子类自己都访问不到。
CInsulateHelp::f_protected;
private:
};
解耦的重写方法就是在CInsulate申明一个CInsulateHelp的指针即可。当然指针在内部就需要注意operator=,拷贝,析构指针的用法了。可以使用scoped_ptr,shared_ptr来明确所有权
2.2 消除嵌入式数据成员
与2.1改写方式类似,使用代理模式即可,将成员变量有对象改写为指针
2.3 消除私有成员函数
因为私有成员函数变量也会导致类变化,如果私有成员函数的功能是辅助函数,我们完全可以通过其它方式解耦合。
通过提供静态函数或则全局函数,当然实现是在cpp中,如果需要使用类中的变量,第一个参数用this指针即可解决。
static void fHelp(CInsulateHelp* pHelp, int)
{
}
2.4 移除保护成员函数
如果基类提供一些保护成员函数,目的是给子类使用,方便子类在实现virtual时调用。这样实际会污染基类,让基类去关心子类,提供帮助给子类了
如
class Base
{
public:
virtual void draw() = 0;
protected:
void drawLine(); // 方便子类画圆
}
改进方案
提供一个专门的辅助类,需要使用包含它,不需要的不用它,减少了基类的负担和耦合,如果需要基类成员变量,提供一个this指针即可
class Scribe
{
pulbic;
void drawLine();
}
2.5 同样是2.4的问题,另外的一种解决方法
如果2.4中单独提取一个辅助类不合适,比如2.4中需要使用this指针去改变成员的值,即子类需要访问的不仅仅是辅助函数,而是实际需要设置基类成员的函数,需要与基类紧密相连的,拆离到单独的类中会将类的职责搞混乱。不太合适分离
我们可以为Base提供一个供客户使用的纯虚接口类,只提供draw
再提供一个BaseImpl继承自Base,提供一些真正要设置成员的函数,真正的实现再继承自BaseImpl。这样BaseImpl的变化,不会影响客户的编译,而子类又使用了各个辅助函数,或则设置函数。
2.6 移除编译器生成的函数
2.7 移除包含指令
2.8 删除默认参数
2.9 删除枚举类型
1.私有枚举类型可以放到cpp中
2.枚举常量可以通过static const int代替,然后提供static的访问函数,这样值变化,不会导致其它文件重新编译。但是可能会影响性能,因为多一个间接访问函数
3.枚举用作返回值,参数类型,尽量定义在类的内部,不要试图去重用它们,不用把它们和其它类似的放在一块。如该文件的错误码就放在该文件中,不用把所有错误码放在一个文件中
3. 整体的隔离技术
3.1 协议类。即抽象接口类,是一个近乎完美的隔离器
不包含任何成员变量,不继承任何类
有一个virtual的析构函数
其它成员,全都是纯虚的