Delegate Class- -| 回首页 | 2005年索引 | - -相等性判断的自动化

属性也应该是接口- -

                                      

面向对象理念中一个低层次的概念叫封装,封装是为了信息隐藏。信息隐藏是一种理念或者说是一种笼统的概念。具体操作起来,首当其冲需要被隐藏的就是属性。

所以,先行者早就告诫我们,应该把一个类的所有属性都设置为私有的,然后提供其set/get方法。比如:

public class Money {
   private double dollar;
  
   public double getDollar() {
      return dollar;
   }
  
   public double setDollar(double dollar) {
      this.dollar = dollar;
   }
}

这样做的好处是,如果有一天我们对这个类进行改变时,可以尽量少的影响这个类的client。拿上面的例子而言,如果有一天,我们我不再想使用美元作为存储,而是换为人民币,我们可以将类的实现改为:

public class Money {
   private double rmb;
   private static double exchangeRate = 8.3;

   public double getDollar() {
      return rmb/exchangeRate;
   }
  
   public double setDollar(double dollar) {
      this.rmb = dollar*exchangeRate;
   }

   public static double setExchangeRate(double rate) {
      exchangeRate = rate;
   }
}

我们可以把方法理解为接口,把属性看作实现。这种把属性隐藏起来的行为就是面向对象设计中最核心的理念:将接口与实现分离,对接口进行编程。

但是,在具体操作的时候,程序员非常厌倦于为每个属性实现默认的set/get方法。实在的讲,这确实不是一件有趣的事情。但为了迎合这种理念,却又不得不硬着头皮,一边诅咒着,一边安慰自己——这样做对未来会有潜在的好处,但,谁知道这种该死的好处什么时候才能出现。谁会喜欢为未知的事情买单呢?

到了需要改变这一切的时候了:编程应该是一件有趣的事情,我们不应该让程序员宝贵的时间和热情花费在这种毫无生趣的地方,这些高度模式化的东西应该交给编译器去做。

传统的OO编程语言将属性看作实现细节,我们首先要改变这种观念:属性也可以是一种接口。

既然是接口,它就必须具备应对变化的能力。所以,我们应该允许程序员变更访问它的方式。而对一个属性的访问方式正是set/get,也就是读取和赋值。我们以上面的例子来展示一下新的方式:

public class Money {
   public double dollar;
}

public class client {
   Money money = new Money();
   public void doSomething() {
      money.dollar = 10;
      double dollar = money.dollar;
   }
}

到现在为止,你一定会质疑,你到底在搞什么?这不就是把属性暴露给client了吗,你在破坏封装。别急,好戏在后面——

当我们需要将dollar变为rmb时,我们可以将类的实现变为:

public class Money {
   private double rmb;
   private static double exchangeRate = 8.3;

   public derived double dollar {
      set{
         this.rmb = value*exchangeRate;
      }
      get {
         return rmb/exchangeRate;
      }
   }
}

注意,我们在新版本的Money类中仍然提供了dollar属性,按照需求,dollar是不应该作为一个实际的属性存在的,所以我们在前面使用derived关键字来说明这是一个导出属性。导出属性本身不会占用任何存储空间。

从这个示例可以看出,我们可以为一个导出属性提供set/get方法,client对其进行读取/赋值操作时,其get/set方法会被自动调用。比如:

  Money money = new Money();
  money.dollar = 3.4; // set方法被潜在的调用;
  double dollar = money.dollar; // get方法被潜在的调用;

另外,指明一点,在set方法中,我们使用value关键字来访问传入的参数。

我们已经看出,我们通过提供导出属性的概念,为属性提供了应对变化的能力,让属性具备的接口的能力。这种思路还可以进一步延伸。

我们先来看一看这个用传统的方法实现的例子:

public class Male {
   private Female wife;

   public void setWife(Female lady) {
     if(lady != null) {
        if(lady.getHusband() == null) {
    lady.setHusband(this);
        }
        else {
    assert lady.getHusband() == this;
        }
      }
      this.wife = lady;
   }

   public Female getWife() {
      if(this.wife != null)
       assert this.wife.getHusband() == this;
      return this.wife;
   }

   public void devorce() {
      if(this.wife() != null)
         this.wife().setHusband(null);

 setWife(null);    
   }
}

public class Female {
   private Male husband;

   public void setHusband(Male man) {
      if(man != null) {
         if(man.getWife() == null) {
            man.setWife(this);
         }
         else {
            assert man.getWife() == this;
         }
      }
      this.husband = man;
   }

   public void getHusband() {
      if(this.husband != null)
         assert this.husband.getWife() == this;
      return this.husband;
   }

   public void devorce() {
      if(this.husband() != null)
         this.husband().setWife(null);

 setHusband(null);    
   }
}

这个例子比较复杂,因为Male的wife属性,以及Female的husband属性的set/get方法并不仅仅是默认的方式,它们做了更多的事情。基于这样的需要,我们也应该允许程序员为非导出属性提供set/get方法。下面是上面例子的Male class以新方式进行的实现。

public class Male {
   public Female wife {
      set {
         if(value != null) {
            if(value.husband == null)
               value.husband = this;
            else
               assert value.husband == this;
         }
         this.wife = value;
      }
      get {
         if(this.wife != null)
            this.wife.husband == this;
         return this.wife;
      }
   }
   public void devorce() {
      if(this.wife != null)
         this.wife.husband = null;
      this.wife = null;
   }
}

好的,现在我们已经有了两种属性,普通属性和导出属性,它们都可以有自己的set/get方法。还可以有更多类型的属性吗?我们再来看一个以传统方法实现的例子:

public abstract class Operation {
   public abstract boolean isAbstract();
   ...
}

public class ClassOperation extends Operation{
   private boolean isAbstract = false;

   public boolean isAbstract() {
      return this.isAbstract;
   }
   ...
}

public class InterfaceOperation extends Operation{
   public boolean isAbstract() {
      return true;
   }
}

在这个例子中,抽象基类Operation中的isAbstract方法被两个子类赋予了不同的实现。ClassOperation是以查询属性isAbstract值的方式来实现的,而InterfaceOperation则总认为自己是abstract的。

isAbstract是一个查询方法接口,按照我们新的观念,属性同样也是一种接口,并且属性的get方法本身就是无参数的查询方法接口,为什么我们不同在基类中将isAbstract实现为属性呢?

问题在于,非导出属性会造成存储空间的占用,而导出属性又限制了属性本身实现的方式,这对于Operation类中对于isAbstract()方法的抽象实现的概念都不吻合,所以我们需要第三种概念:抽象属性。

抽象属性仅仅指定了属性的存在,并提供了属性的访问接口,具体到继承类中,你可以用普通属性来实现,也可以以到处属性来实现。抽象属性并不关心具体的实现方式。

看一看上面例子的新实现:

public abstract class Operation {
   public abstract boolean isAbstract { get; } // readonly
   ...
}

public class ClassOperation extends Operation{
   public boolean isAbstract = false;
   ...
}

public class InterfaceOperation extends Operation{
   public derived boolean isAbstract {
      get { return true; }
   }
   ...
}

Operation op1 = new ClassOperation();
if(op1.isAbstract) // ClassOperation的isAbstract的值被读取;
  ...
Operation op2 = new InterfaceOperation();
if(op2.isAbstract) // InterfaceOperation的导出属性isAbstract的get方法被调用。
  ...

鉴于抽象属性的性质,我们也可以在Interface中定义抽象属性:

public interface Classifier {
  boolean isPublic { get; } // abstract, readonly
  ...
}

这就是对于属性的新的概念。所有这些概念都围绕着一个基本的思想:属性也是一种接口。这一点与过去把属性看作实现从本质上有着完全的不同。当属性也是一个接口的时候,程序员就可以更加轻松,更加灵活的对系统进行构造。

- 作者: 上帝没发笑 2005年01月31日, 星期一 04:32 加入博采

Trackback

你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=650138

回复

评论内容: