所遇不良设计(一)
Table of Contents
我们在设计时,一定要考虑系统的未来的可扩展性,为未来做好准备,预备接口。现在软件的设计为了降低其耦合度,大多将软件设计成插件模式。
1 扩展的常量
系统中出现大量的常量,随着系统模块的添加,会不断增加这些常量; 比如说日志的类型、货币的类型等等。而日志是每个模块必定调用的模块,货币在好多涉及到支付的模块里面会被调用,当我们要增加一个类型的时候,往往牵涉到其他模块,当编译的时候,其他模块也要相应的重新编译。增加一个宏,花了半个小时去编译,在项目中,我看到很多人抱怨。
我遇到的问题(货币的常量):
enum CURRENT_TYPE{ GOLD_TYPE; . . . COIN_TYPE; ... }
我认为,将要扩展的常量丢到配置文件里面,这样可能在速度以及存储上带来额外的开销。我觉得对系统性能没多大的影响,配置文件一般在系统开启时加载好。
"gold"=0 . . . "coin"=n ....
将上述配置加载到hashmap中去,这样子在调用的时候使用hashmap["gold"]来获取其类型。有人(可能是一个acmer)就会说这没有宏高效,的确如此啊; 但是货币的数量毕竟是少的,这点速度牺牲不算什么,换来了灵活的可扩展性。
注明: 一些结构不复杂的常量设置,我并不是很喜欢xml,xml的表现太过复杂; 我们能不能选择像httpd.conf(Apache的配置文件)那样简单明了。这可能也只是我的一厢情愿罢了,首先游戏的模板数据比较复杂,涉及到嵌套,这样简单的属性配置文件是表现不出数据关系的。同时为了达到统一性,最终会还是xml比较好。
2 多变的函数
有时候,我们要调用上乘的接口,发现没有我们要调用的接口,必须要到上层模块里面添加适合我们业务的接口; 每遇到这种相似的业务,都要如此。这就说明上层的设计不够完美。要想想我们要让机器人能够使用锤子、钳子 扳手,我们是不是要拆卸它的手臂,给它换一个手臂呢? 这样很麻烦,我们何不为其设计接口,给它换上什么器械,它就能干不同的活。在C++里面使用多态,也可以使用模块; 在C里面就是传送同一类型的函数指针。
C++的第一种模式:
//底层 class Base { virtual T Func (Arg* arg); }; void Interface (Base* a, Arg* p_arg){ //这是接口 a->Func(p_arg); } //扩展 class ABase : public Base { virtual T Func (Arg* p_arg); } ABase* pABase; Interface(pABase, p_arg);
我们只需要不断的继承Base并且重新写Func就能给Interface换上新的“器械”了,这样子不用更改底层的代码,导致重新编译。
C++的第二种模式:
template <class T> void Interaface(T* p_t, Arg* p_arg){ //这是接口 p_t->Func(p_arg); }
只要写一个类型并且实现Func函数,不需要继承任何东西,就给Interface换上新的“器械”了,同样也减少了编译时间。
总结:上述C++的两种方法,我更加倾向于运用多态,运用模板,你就必须把你的类申明和实现全部丢到头文件里面,这样子发布API的时候,别人会看到你的实现,而且头文件会显得臃肿。
我们再来说说C的实现,C的实现很简单,就是传送相同的函数指针。
void Interface (T (*) (Arg* p_arg))
这样子,你只需要在外部重写这个函数传进去,就OK了,函数名字可以不一样,但是类型不一样,也是一种很酷的方法。其实这种方式我们在STL的一些库函数里面经常看到,比如遍历:
void Traverse (iterator<T> beg, iterator<T> end, void (*oper) (T& a));
只需要变换oper,我们可以对容器里一定范围内的元素进行显示,变化他们的值等。
3 强势的friend
哥们你要调用我,得要先申请friend,一副盛气凌人的样子。我要说可能每个人负责某个模块,谁会料到某人的离职或者生病了,的确每个人写的模块,其本人是最清楚的。整个项目是整个团队的,而不是某个人的。有些人喜欢将自己本来应该开放给其他模块的API设置为protected和private; 以为这样会很安全,禁止别人调用,我觉得调用又如何了,又不是会涉及到支付以及破坏系统,更何况后台系统,不会把API暴露给外部,只是本系统内部使用。况且为了保护自己的API,把其设置为protected,我觉得是隔靴搔痒。看看我下面是如何破解,哥们我不求你把我当做friend:
底层系统类A,我们需要调用其Func。
class A { protected: void Func() { .... } }
我们的破解招数如下:
class B:public A { public: void Func(){ A::Func(); } } B b; b.Func()
虽然这样子绕了一个圈子,不如直接调用来得好,我们还是没有做friend,就获取了A的资源; 要做到保护,做绝点,应该就是private了。哥们要放低架势,要是好多模块都要调用你的模块,每次要在你的头文件里面加入friend,会导致大量重新编译的。这也是我讨厌friend的原因,要做咱就做好基友或者好闺蜜。
4 层叠的工程
做项目的过程中,大家肯定遇到过这样问题,就是遇到指针都要判断空,形成了强迫症,就是怕访问到空指针,导致系统崩溃的。即使是底层已经帮我们做了,我还是要判断。我们先来看看情形吧。
bool Func1 (Player* pPlayer, ...) { if (NULL == pPlayer) { return false; } ... } bool Func2 (Player* pPlayer, ...) { if (NULL == pPlayer) { return false; } ... if(false == Func1(pPlayer, ....) { return false; } ... } bool Func3 (Player* pPlayer, ...) { if (NULL == pPlayer) { return false; } ... if(false == Func2(pPlayer, ....) { return false; } ... }
首先,调用接口者,可能没有去研究所调用的接口吧,还有可能有那种强迫症吧,才如此做的吧。程序员主要是怕系统崩溃了,找到自己的头上吧。如果应要判断空,应该整个系统来约定一下,最上层来判断或者最底层判断。其他时候不判断的; 我还是觉得应该由底层来判断的,这样子能够让系统更加健壮。还有我觉得可以不用返回false的,其实一个函数里面返回false的因素比较多,返回给外部都不知道里面到底是什么原因导致的false。我觉得做成异常抛出可能更加好一些,不同因素导致返回失败,抛出不同的异常,这样子好跟踪错误。同时这样子约定,如果该函数不需要处理异常,那也把异常给扔出去,直到谁调用要处理异常,才try…catch。