(李林老师linux环境高级编程课后笔记)
架构的核心就是做到代码封闭性,即来了新的需求不用改老代码,只用增加新代码
核心要素:来一个变化点就新建一个体系结构
---李林老师
背景是实现一个加法器,对象中有一个数据成员作被加数,一个成员函数函数传入加数,返回和。
此加法器类实现起来很简单,如下代码
#include <iostream>
using namespace std;
class CLAdder
{
public:
explicit CLAdder(int iAugend)
{
m_iAugend = iAugend;
}
int Add(int iAddend)
{
return m_iAugend + iAddend;
}
private:
int m_iAugend;
};
int main()
{
CLAdder adder(2);
cout << adder.Add(4) << endl;
return 0;
}
现在来了一个变化点(新的需求):需要实现带权重的加法器的功能。
即输出 被加数*权重+加数
此时用 来一个变化点就新建一个继承体系 的思想,用c++面向对象思想来实现。
-
面向对象的思想(继承+多态)
#include <iostream>
using namespace std;
class CLAdder
{
public:
explicit CLAdder(int iAugend)
{
m_iAugend = iAugend;
}
virtual ~CLAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend + iAddend;
}
protected:
int m_iAugend;
};
class CLWeightingAdder : public CLAdder
{
public:
CLWeightingAdder(int iAugend, int iWeight) : CLAdder(iAugend)
{
m_iWeight = iWeight;
}
virtual ~CLWeightingAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
protected:
int m_iWeight;
};
int main()
{
//基类对象(不带权重的加法器)
CLAdder adder(2);
//基类指针指向基类对象
CLAdder * p = &adder;
cout << "CLAdder:" << p->Add(4) << endl;
//派生类对象(带权重的加法器)
CLWeightingAdder wadder(3, 4);
//基类指针指向派生类对象
p = &wadder;
cout << "CLWeightAdder:" << p->Add(4) << endl;
return 0;
}
c++中的多态思想,核心就是 基类指针指向派生类对象 ,c++的这个多态性质是虚函数机制支撑的。具体可以看另一个笔记:虚函数和虚表初步
- 这种程序设计思想在架构中的好处
在测试代码中,用户都是对基类指针进行操作,来了变化点也是对p进行操作,在复杂的业务环境中如
fun(CLAdder * p)
{
...
}
中,对fun的使用,即使来了变化点(加了权重),也不用对原本的fun函数进行修改,照样可以用,只要传进去的是派生类对象就可以,即做到了代码的封闭性,只加新代码不改老代码。
但是,这种设计方法也有不足的情况和更好的方案及基于接口的思想
此时又来了一个变化点,若基本的加法器是只能由两个无符号整数相加,这时实现一个有符号整数的、带权重的加法器。诚然,依据来一个变化点就加一个继承体系的思想,可以再接着设计一个派生类,加一个有符号数的数据成员,和自己的加法函数。但是,这样的话,派生类就继承了不必要的数据成员,产生了很多不必要的浪费。这样的设计却是犯了过度设计的缺点,并不是所有的变化点都是需求,此时有更好的方案。即基于接口的思想
-
基于接口的思想
#include <iostream>
using namespace std;
class ILAdder
{
public:
ILAdder()
{
}
virtual ~ILAdder()
{
}
virtual int Add(int iAddend) = 0;
};
class CLAdder : public ILAdder
{
public:
explicit CLAdder(unsigned int iAugend)
{
m_iAugend = iAugend;
}
virtual ~CLAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend + iAddend;
}
private:
unsigned int m_iAugend;
};
class CLWeightingAdder : public ILAdder
{
public:
CLWeightingAdder(int iAugend, int iWeight)
{
m_iWeight = iWeight;
m_iAugend = iAugend;
}
virtual ~CLWeightingAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
private:
int m_iAugend;
int m_iWeight;
};
void f(ILAdder *pAdder)
{
cout << pAdder->Add(4) << endl;
}
int main()
{
CLAdder adder(2);
f(&adder);
CLWeightingAdder wadder(3, 4);
f(&wadder);
return 0;
}
在此思想中用到了c++中的另一个机制,虚基类和纯虚函数。虚基类的实现为空,成员函数都为纯虚函数,就是要给子类重写用的。
-
这种设计思想的好处
在测试代码和原来的程序中,f()函数的参数类型是虚基类即接口的指针,根据传入的对象类型不同,调用的是不同的加法器的函数。并没有在来了需求后改老的代码( f() ),也做到了代码的封闭性。
对比和收获
- 本质上都是多态的思想,面向对象的思想我理解其实是继承的思想,基于接口的思想是把原来的父子关系变成了兄弟关系。
- 面向对象的思想是拿着基类指针来干活,基于接口的思想是拿着虚基类即接口的指针来干活。
- 继承的思想是一种强耦合关系,无论需不需要,子类都完全继承了基类的数据成员,基类变了,所有的派生类都要变,耦合与接口(函数),耦合与实现(数据成员)。所以能少用继承就少用继承。
- 想起以前设计类的时候,只会用继承关系,当时就觉得基类有些数据成员和函数其实派生类并不需要,但是又觉得派生类确实是要跟基类有联系的,就无脑用了继承,现在觉得耦合度确实太强了,并且会造成不必要的资源消耗。以后可以考虑接口的思想。
架构本质上就是解耦的过程,但是会增加很多的中间结构,可能会降低性能,需要在设计的时候来权衡。
网友评论