|
1 概述
本博客的目的是总结自己编写C++程序的一些经验教训,为同仁提供一些参考。设计模式的重要性就不必说了。在经典的《设计模式-可复用面向对象软件的基础》一书中,四位作者以GUI软件作为例子,用C++98标准阐述了23种设计模式。但是2011年后,现代C++标准做了很大的变革,编程范式完全升级了。这本书并没有同步到C++11标准。市面上关于现代C++标准的书有一些,但是基于现代C++标准全面阐述设计模式的,至少我目前还没有见到。我这篇博客主要针对我自己用过的一些模式,提出一些实现和想法,因此肯定是不全面的。本博客的代码例子都是我自己写的,一定存在考虑不周之处。
由于现代C++的影响,国内影响力的作者对于设计模式、面向对象的思想做了很强有力的反思。例如陈硕的著作《Linux多线程服务端编程》;孟岩的博客《function/bind的救赎》等。我个人认为,可以不把设计模式绝对化,只要是好的经验,都可以是设计模式。设计模式也并不一定是面向对象的。例如,STL是典型的模板-泛型设计,不是面向对象的,这个似乎也可以作为通用库的一种设计模式。
设计模式究竟有很大的必要性吗?初学者读《设计模式》这本书,经常难以读懂,实际上,设计模式本身没有任何技术复杂性,大家觉得难是因为不具备这样的思维方式。当你真正有大量的开发经验的时候,就知道设计模式的重要性了。你见过有的程序有几百个全局变量,把所有的功能模块都耦合在一起,让你重构时无从下手吗?你见过混乱的继承体系,把原作者都绕晕的吗?你见过一个cpp文件有几万行,一个函数就有几千行吗?见得多了,就知道设计模式的重要性了。
下面我尝试从生理学的角度解释设计模式的作用。人的大脑思考机制,有些像计算机缓存-内存的机制。人在快速思考中不能处理过多的对象,如果思考的对象很多很复杂,需要把长期记忆区中的信息调取到缓存区,把缓存区的暂时不用的东西冲掉。长期记忆区与缓存区的切换越频繁,工作效率就越下降,而且下降的幅度很大。考虑一个设计糟糕的程序,你加一个简单的功能,都要修改十几处代码,那么,每切换一次修改位置,大脑都要重新建立该位置的工作场景和缓存;如果程序是良好设计的,有好的设计模式,那么,增加一个新功能,只需要在一个地方增加代码。这两者的工作效率差别非常惊人。有的大公司开发一个软件,几十个人干了一两年都不理想;同样功能的软件,小公司几个人就开发出来,而且打败了大公司,这样的例子实在太多了。
还可以从建筑的角度来类比。事实上,软件的结构与建筑的结构非常相似,设计模式这个词汇最初也来源于建筑。建设一个建筑物需要很多工人,每个工人职责范围都是很有限的。你看不到哪个工人一会在地基工作,一会儿又跑到顶楼;一会儿搅拌混凝土,一会儿开吊车。但是在软件工程领域,真的很多项目就是这样做事情的呢!
2 回调函数与function/bind
大家都熟悉调用函数,但是对回调却通常不熟悉。其实这涉及一个思维方式的转换。C++之父在《C++程序设计原理与实践(第2版)(进阶篇)》有一张图经典的说明函数调用和函数回调的区别。
常规函数与回调函数的主要区别是:常规函数何时调用,是函数本身的实现者可以决定的。而回调函数,是函数本身的编写者无法决定何时调用。上图GUI的例子是很典型的,你在GUI程序中设计一个点按钮功能的函数,但是你却不能决定何时调用这个函数。你必须设计成回调函数,注册到系统里面,由用户点击调用。
在C++11中,提供了function/bind机制,可以实现回调函数。把函数调用的行为绑定再转换成function对象,然后对function对象就可以进一步处理了。(包括可以传值到函数里面,暂时存储下来,等等)。这种机制带来了巨大的灵活性,相当于行为机制的抽象化。function/bind为设计模式中的行为模式,提供了一种更简化的实现方法。
在经典的《设计模式》一书中,反复强调:要多用组合,少用继承。因为继承是强耦合的,一旦设计确定,后面就不好修改了。尤其是你设计出庞大的、有几百个类的继承体系,突然发现这个设计有重大缺陷,或者新的需求用已有的继承体系很难处理,那需要很大的重构成本。
但是传统设计模式对于虚函数的继承依然无法完全避免,因为C++的运行时多态只有这种处理方法。但是function/bind提供一种新的选择,我们可以只有具体类,没有继承,照样可以实现运行时多态。下面给出简单的例子(每个人都可以轻松看懂),分别:
#include<iostream>
#include<memory>
using namespace std;
class Base
{
public:
virtual void method() = 0;
};
class Base_A:public Base
{
public:
void method()
{
cout << "method_A" << endl;
}
};
void Do_somthing(shared_ptr<Base> base)
{
base->method();
}
int main()
{
shared_ptr<Base> base = make_shared<Base_A>();
Do_somthing(base);
}
上面这是采取虚函数继承的方式。
#include<iostream>
#include<functional>
using namespace std;
class Base_A
{
public:
void method()
{
cout << "method_A" << endl;
}
};
void Do_somthing(function<void()>f)
{
f();
}
int main()
{
Base_A base_;
Do_somthing(bind(&Base_A::method,&base_));
}
这个是采取function/bind的方式。采取function/bind可以简化继承体系,降低功能模块的耦合度。
无论是哪种方式,关键点是Do_something函数与具体的实现解耦,以后如果新增了Base_B方法,无需修改Do_something函数。
Archiver|手机版|科学网 ( 京ICP备07017567号-12 )
GMT+8, 2024-12-8 10:35
Powered by ScienceNet.cn
Copyright © 2007- 中国科学报社