跳转到内容

面向对象编程/状态之恶

来自维基教科书,开放世界中的开放书籍

"状态"是邪恶的!

[编辑 | 编辑源代码]

我们没有足够强调的一点:状态(与变化相反)是邪恶的!或者,(也许更好的说法)维护不必要的状态是所有(呃……许多)错误的根源。让我们看一个例子,现在用 Ruby

  1. 我们正在随时间显示某些内容,因此我们正在处理
  2. 像素和秒。根据缩放级别,
  3. 它们之间存在相关性:每秒像素,
  4. 或 PPS。

类时间线

{
 PPS;
 current_position_in_seconds;
 current_position_in_pixels;
public:
 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s) {current_position_in_seconds = s;} # oops, we're out of sync
 GetPosInPixels() {current_position_in_pixels;}
 SetPosInPixels(p) {current_position_in_pixels = p;} #oops, we're out of sync

} 在此示例中,我们正在维护不必要的状态——也就是说,我们同时以秒和像素来保存位置。这对使用此类的用户来说很方便,也很重要,但我们在封装方面搞砸了。每当您在一个单位中设置值时,都会破坏另一个单位的正确性。您说,这是一个简单的修复方法

  1. 我们正在随时间显示某些内容,因此我们正在处理
  2. 像素和秒。根据缩放级别,
  3. 它们之间存在相关性:每秒像素,
  4. 或 PPS。

类时间线 {

 PPS;
 current_position_in_seconds;
 current_position_in_pixels;

public

 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s)
      {
         current_position_in_seconds = s;
         current_position_in_pixels = s*PPS;
       }
 GetPosInPixels() {current_position_in_pixels;}
 SetPosInPixels(p)
      {
         current_position_in_pixels = p;
         current_position_in_seconds = p/PPS;
      }

} 这修复了前面示例中明显的错误,但您为自己制造了很多工作。现在您必须在 Timeline 类中的每个方法中保留所有计算的副本以保持一致性。这里可能还有几十个这样的情况(相信我)。那么,当您添加其他单位时,例如英寸?您必须再次复制所有内容。大概您会明白这将走向何方:计算属性、逻辑属性、虚拟属性,无论您喜欢怎么称呼它们。让我们再次看看这个例子

类时间线 {

 PPS; # Pixels Per Second
 IPS; # Inches Per Second
 current_position_in_seconds;

public

 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s) {current_position_in_seconds = s;}

 GetPosInPixels() {current_position_in_seconds*PPS;}
 SetPosInPixels(p) {current_position_in_seconds = p/PPS; }

 GetPosInInches() {current_position_in_seconds*IPS;}
 SetPosInInches(i) {current_position_in_seconds = i/IPS; }

} 现在,我们假设 PPS 和 IPS 的转换因子设置正确,以方便起见,但除此之外,我们已经大大简化了问题。在我们现在编写的另外二十个函数中,我们只需要担心秒,没有一致性问题需要担心。此外,如果我们需要将基本单位从秒更改为像素,Timeline 用户永远不会知道。

华夏公益教科书