精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-12-02
最后修改:2009-12-03
最近,忙于思考如何重构一个Unix系统。这个系统是由C++写的,重构的思路是希望能够抽取出一些公共的东西,作为Core。另外一些东西做成Plug-in。这样以后如果客户的规范更新了,我们只需要增加修改Plug-in就好了,Core的部分不需要再修改。
要满足这个要求,有些基本的原则: [1] Core里面所有的东西不能依赖Plug-in里面。这种依赖可以理解为Build(Compiler + Link)Core里面模块的时候,不需要用到Plug-in里面的头文件或者object文件; [2] 部署的时候,可以不用重新部署Core。只需要Update Plug-in,就可以获得新功能。 [3] 现有代码最好能够最大程度的复用。 因为这些要求,对C++设计编码在大型系统中的使用,有了一些新的体会和认识。为了简化问题,我抽象了一个简单的例子。
有这样一个需求: 给定一个整数数组,我们需要对这个数组作些运算,这个运算现在看到的有求和,求积,未来可以增加运算规则,譬如求最大值,最小值,平均值等等。现在不能预测未来需要那些运算,系统需要扩展的能力。
首先来看看运用面向对象和工厂模式给出的初始设计。 我们有一个ICalc的借口,接口里唯一一个需要Override的函数是: virtual void calc(int *p, size_t size, int *result) = 0 这里说唯一是从设计角度考虑的,因为它本身的析构函数也是纯虚函数。另一方面,这个函数没有要求是const的,这个不会影响我们的讨论。因为现有系统中并没有一致的使用const,所以我们设计这个接口的时候,没有把const考虑进去。 接下来,我们设计两个实体类CalcAdd和CalcMul,当然它们都Override了calc这个方法。我们又设计了一个CalcFactory,它有个静态函数getCalc(string calcType)。通过calcType的值,返回正确的Calc类型。
// include/ICalc.h #ifndef _ICALC_H_ #define _ICALC_H_ #include <stdlib.h> class ICalc { public: virtual void calc(int *start, size_t size, int *result) = 0; virtual ~ICalc() = 0; }; #endif //_ICALC_H_
//include/CalcFactory.h #ifndef _CALCFACTORY_H_ #define _CALCFACTORY_H_ #include <string> #include "ICalc.h" class CalcFactory { public: static ICalc* getCalc(std::string calcType); }; #endif //_CALCFACTORY_H_
//include/CalcAdd.h #ifndef _CALCADD_H_ #define _CALCADD_H_ #include "ICalc.h" class CalcAdd : public ICalc { public: virtual void calc(int *start, size_t size, int *result); virtual ~CalcAdd(); }; #endif //_CALCADD_H_
给纯续函数提供函数体是可以的,在这种情况下也是必需的。 #include "../include/ICalc.h" ICalc::~ICalc(){ }
// src/CalcFactory.cpp #include "../include/CalcFactory.h" #include "../include/CalcAdd.h" #include "../include/CalcMul.h" using std::string; ICalc* CalcFactory::getCalc(string calcType) { if ("Add" == calcType) { return new CalcAdd; } else if ("Mul" == calcType) { return new CalcMul; } else { return 0; } }
// src/CalcAdd.cpp #include "../include/CalcAdd.h" void CalcAdd::calc(int *p, size_t size, int *result) { for (size_t i = 0 ; i < size; ++i) { (*result) += p[i]; } } CalcAdd::~CalcAdd() { }
// src/main.cpp #include <iostream> #include <string> #include "../include/CalcFactory.h" #include "../include/ICalc.h" using namespace std; int arr[] = { 1, 2, 3, 4, 5, 6 }; int result; // result = 0 int main(int argc,char* argv[]) { if (argc < 2) { cerr << "Usage: calc Add|Mul" << endl; return -1; } if (string("Add") == argv[1]) { result = 0; } else if (string("Mul") == argv[1]) { result = 1; } ICalc *calc = CalcFactory::getCalc(argv[1]); if (!calc) { cerr << "Fail to calc." << endl; return -2; } calc->calc(arr, sizeof(arr)/sizeof(int), &result); cout << "result = " << result << endl; delete calc; return 0; }
大致的程序如上述,上面没有给出CalcMul.h和CalcMul.cpp。因为这个和CalcAdd完全一致。大家可以下载v1.tar.bz2看最初设计的源代码。大家可以使用make -f Makefile.(mac|sun|lnx) all Build整个系统,最后会在bin下面生成一个可执行文件calc,运行就可以看到结果。 可以看到,这个版本其实是个不错设计的。因为如果我们要增加一个求最小值的算法,只需要设计一个CalcMin,实现ICalc接口,重新修改CalcFactory的getCalc函数,增加一个“Min”的分支,就OK了。这个设计也符合开闭原则。就是对修改开放,对整个工作流的修改是关闭的。可是,这样的设计还是有几个问题: [1] CalcFactory.obj依赖于CalcAdd.h,CalcMul.h。如果增加一个新的算法,这个文件必须重新修改,编译,链接。 [2] calc虽然不依赖于CalcAdd.h,CalcMul.h。换言之,如果CalcAdd修改了,calc不需要重新编译,但是却需要重新链接。因为calc需要CalcAdd.o, CalcMul.o。 基于这两点,我们没有办法把CalcAdd这样的算法做成Plug-in。因为更新Plug-in,我的Core程序也需要更新。很多时候这不是一个严重的问题,但是有时候我们确实需要简单的更新Plug-in。因为如果只更新Plug-in,我们可以减少很多Core程序测试的时间和工作量。这个在大型程序带来的价值是巨大的。于是,我们继续改进我们的设计。
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 1862 次