再论对象与状态

再论对象与状态

继续上一篇博客(http://www.cnblogs.com/simonblogs/archive/2013/05/23/3095050.html

 

考虑实现一个分数类,class Fraction,

class Fraction

{

  Fraction  Add(Fraction  c);

  Fraction  sub (Fraction  c);

  Fraction  multi (Fraction  c);

  Fraction  divide (Fraction  c);

}

分数的构造函数、加减乘除有个问题,就是算完之后需要简化分子分母使其不能有公约数,考虑 3/8  +  1/8 结果是4/8,应该化简为1/2。

这样加减乘除四个函数的最后应该是调用一个私有method,比如叫 Gcd().

Void Fraction::Gcd(result)

{

      result.demo = xxx;

  result.num = yyy;

}

Gcd()改写了入口参数result,但是这个改写的是中间结果,返回给client的是化简之后的result.所以client是意识不到这个状态的(化简前、化简后)。这里“已化简”是加减乘除的后置条件。如果后置条件总是满足,那么Fraction不是一个状态机,依然是一个值语义。

如果加减乘除不满足那个后置条件,client就需要显示调用gcd()去化简结果。这增加了client的负担,因为client需要保持result的标识。这也破坏了模块性原则,因为client需要耦合一个处理Fraction的method Gcd.

另一方面,加减乘除都满足了后置条件,加减乘除的内部实现也不需要保持一个状态,加减乘除可以说是一个事务,这个事务保证最后commit时,确保结果已经被化简。

总结好的对象设计的原则:

尽可能将状态封装,client只需要感知最少的状态。本例中, Fraction的状态数是0。

一个反面例子就是class的构造函数之后,client马上必须调用一个Init()方法,那是一个很差的设计。

Client内部也尽量减少各个method需要感知的状态,本例中,method内部也没有状态。

Method尽量实现为一个事务,如果method内部抛出异常,尽量使事务回滚,使对象依然处于可用的,逻辑一致的状态。举例说,一个Fraction的构造函数内会设置初始值并且调用gcd化简初始值,如果分母为0应该抛出异常。好的设计是抛出异常在最开始执行,而不是设置完分子值,才发现分母是非法值。回滚事务的意思就是,如果分母非法,请把分子值擦除。

posted on 2013-05-24 06:54  SimonBlog  阅读(135)  评论(0编辑  收藏  举报

导航