在读11.6.5 iostream在设计方面的缺点章节时,作者陈硕提到了iostream在面向对象方面的设计缺陷:面向对象的public继承未满足继承非为复用,乃为被复用的Liskov替换原则。
书中提到iostream的继承体系多次违反了Liskov替换原则,这些地方的继承是为了复用基类的代码,而Liskov原则提到:继承并不是为了复用代码。由此我想到我可能在最近的项目中做了一些XB事情。
在最近的项目中,后台程序开放了多个http接口,这些http接口的body都是json格式,不同的接口参数不一样,如下:
获取余额接口 http://xxx.xxx.xxx.xxx:xx/abcd/getBalance
{
“account”:”a1234”,
“pwd”:”asdfg”
}
发送消息接口 http://xxx.xxx.xxx.xxx:xx/abcd/sendMsg
{
“account”:”a1234”,
“pwd”:”asdfg”,
“extend”:”11”,
“compressType”:”1”,
“content”:”测试一下”,
“userList”:”a0001”,
“flag”:”1”
}
定时发送接口 http://xxx.xxx.xxx.xxx:xx/abcd/timerSend
{
“account”:”a1234”,
“pwd”:”asdfg”,
“extend”:”11”,
“compressType”:”1”,
“content”:”测试一下”,
“sendTime”:”2018-11-11 00:00:00”,
“userList”:”a0001,a0002,a0003”
}
于是我写了如下class
class ParseBase // 基类,这里简单写一下
{
public:
virtual bool parse();
virtual string getFlag() {return “”;}
string account;
string pwd;
string user;
string content;
string extend;
};
class GetBalanceParse : public ParseBase {};
class SendMsgParse : public ParseBase
{
public:
virtual bool parse()
{
ParseBase::parse(); // 这里先调用基类的解析函数
}
virtual string getFlag() {return flag;}
string flag;
};
class TimerSendParse : public ParseBase
{
};
因为在SendMsg接口的业务代码里面,需要获取flag,但是代码中使用的都是基类指针,所以就顺其自然的在基类里面加了个虚函数获取flag字段的值。当然,在其他接口业务代码中,是不会去调用这个getFlag函数的,但这样做也就相当于允许了其他接口的业务代码去调用getFlag函数。
为了复用代码而写的继承体系,违反了适用于base classes身上的每一件事情,也一定适用于derived classes身上的原则。
正确的做法,应该是使用复合(或者组合)来代替。
不采用继承体系,而是写一些容易使用也容易修改的具体类,采用基于对象的设计。如果两个类有共同的代码逻辑,那就考虑一下提炼出一个工具类来用,让代码保持清晰。
class ParseTool; // 原Parse基类改成工具类
class SendMsgParse
{
public:
ParseTool parseTool;
};
class TimerSendParse : public ParseBase
{
public:
ParseTool parseTool;
};