所遇不良设计(一)

  我们在设计时,一定要考虑系统的未来的可扩展性,为未来做好准备,预备接口。现在软件的设计为了降低其耦合度,大多将软件设计成插件模式。

1 扩展的常量

  系统中出现大量的常量,随着系统模块的添加,会不断增加这些常量; 比如说日志的类型、货币的类型等等。而日志是每个模块必定调用的模块,货币在好多涉及到支付的模块里面会被调用,当我们要增加一个类型的时候,往往牵涉到其他模块,当编译的时候,其他模块也要相应的重新编译。增加一个宏,花了半个小时去编译,在项目中,我看到很多人抱怨。

   我遇到的问题(货币的常量):

  enum CURRENT_TYPE{
      GOLD_TYPE;
      .
      .
      .
      COIN_TYPE;
      ...
  }
View Code

  我认为,将要扩展的常量丢到配置文件里面,这样可能在速度以及存储上带来额外的开销。我觉得对系统性能没多大的影响,配置文件一般在系统开启时加载好。

  "gold"=0
  .
  .
  .
  "coin"=n
  ....
View Code

  将上述配置加载到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);
View Code

  我们只需要不断的继承Base并且重新写Func就能给Interface换上新的“器械”了,这样子不用更改底层的代码,导致重新编译。

  C++的第二种模式:

  template <class T>
  void Interaface(T* p_t, Arg* p_arg){ //这是接口
      p_t->Func(p_arg);
  }
View Code

  只要写一个类型并且实现Func函数,不需要继承任何东西,就给Interface换上新的“器械”了,同样也减少了编译时间。

  总结:上述C++的两种方法,我更加倾向于运用多态,运用模板,你就必须把你的类申明和实现全部丢到头文件里面,这样子发布API的时候,别人会看到你的实现,而且头文件会显得臃肿。

  我们再来说说C的实现,C的实现很简单,就是传送相同的函数指针。

  void Interface (T (*) (Arg* p_arg))
View Code

  这样子,你只需要在外部重写这个函数传进去,就OK了,函数名字可以不一样,但是类型不一样,也是一种很酷的方法。其实这种方式我们在STL的一些库函数里面经常看到,比如遍历:

  void Traverse (iterator<T> beg, iterator<T> end, void (*oper) (T& a)); 
View Code

  只需要变换oper,我们可以对容器里一定范围内的元素进行显示,变化他们的值等。

3 强势的friend

  哥们你要调用我,得要先申请friend,一副盛气凌人的样子。我要说可能每个人负责某个模块,谁会料到某人的离职或者生病了,的确每个人写的模块,其本人是最清楚的。整个项目是整个团队的,而不是某个人的。有些人喜欢将自己本来应该开放给其他模块的API设置为protected和private; 以为这样会很安全,禁止别人调用,我觉得调用又如何了,又不是会涉及到支付以及破坏系统,更何况后台系统,不会把API暴露给外部,只是本系统内部使用。况且为了保护自己的API,把其设置为protected,我觉得是隔靴搔痒。看看我下面是如何破解,哥们我不求你把我当做friend:

  底层系统类A,我们需要调用其Func。

  class A {
  protected:
      void Func() {
          ....
      }
  }
View Code

  我们的破解招数如下:

  class B:public A {
  public:
      void Func(){
          A::Func();
      }
  }
  
  B b;
  b.Func()
View Code

  虽然这样子绕了一个圈子,不如直接调用来得好,我们还是没有做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;
      }
      ...
  }
View Code

  首先,调用接口者,可能没有去研究所调用的接口吧,还有可能有那种强迫症吧,才如此做的吧。程序员主要是怕系统崩溃了,找到自己的头上吧。如果应要判断空,应该整个系统来约定一下,最上层来判断或者最底层判断。其他时候不判断的; 我还是觉得应该由底层来判断的,这样子能够让系统更加健壮。还有我觉得可以不用返回false的,其实一个函数里面返回false的因素比较多,返回给外部都不知道里面到底是什么原因导致的false。我觉得做成异常抛出可能更加好一些,不同因素导致返回失败,抛出不同的异常,这样子好跟踪错误。同时这样子约定,如果该函数不需要处理异常,那也把异常给扔出去,直到谁调用要处理异常,才try…catch。

posted @ 2014-03-16 00:32  CharellkingQu  阅读(785)  评论(0编辑  收藏  举报