2011年3月25日 星期五

2011年3月24日 星期四

漫談物件導向(十二) - 再談資料繼承

再談資料繼承

先前提到基於封裝性 (encapsulation)最好將資料成員 (屬性) 設定為private,而由成員方法管控資料成員的存取權。在介紹繼承時,也指出宣告為private之成員方法只有自身類別可以使用,那麼子類別會不會繼承父類別的private資料成員?

其實子類別內還是存在著來自父類別的private資料成員,只是不允許直接使用這些資料成員,除非父類別有提供方法來存取這些private資料成員。檢視下列程式碼中,BALL類別的radius資料成員是private性質,BASKETBALL類別繼承BALL類別,自然還是具備radius屬性,只是不能直接取用,因此程式碼第15行作法是不被允許的;但BASKETBALL類別仍可透過繼承自BALL類別的getRadius()方法來取得radius值 (如程式碼第16行)。從現實生活的角度來看,籃球既然繼承球類別,隱含具備半徑屬性是合理的。

1.    public class BALL {
2.        private float radius;
3.        public BALL (float r) {
4.            this.radius = r;
5.        }
6.        public float getRadius() {
7.            return radius;
8.        }
9.    }
10.  public class BASKETBALL extends BALL {
11.      BASKETBALL (float r) {
12.          super(r);
13.      }
14.      public void showinfo() {
15.          System.out.println(this.radius);           //錯誤
16.          System.out.println(this.getRadius()); //正確
17.      }
18.  }

2011年3月23日 星期三

漫談物件導向(十一) - 多型性

多型性polymorphism

維基百科對多型(英語:Polymorphism)一詞的解釋是「物件導向程式執行時,相同的訊息可能會送給多個不同的類別之物件,而系統可依據物件所屬類別,引發對應類別的方法,而有不同的行為」。參照下列程式碼,比較能清楚說明多型的機制。

1.        class OOTest {
2.          public static void main(String[] args) {
3.            SHAPE sobj = null;
4.            CIRCLE c1 = new CIRCLE(3.0F);
5.            RECTANGLE r1 = new RECTANGLE(3.0F, 4.5F);
6.            sobj = c1;
7.            sobj.showinfo();
8.            System.out.println ("area = " + sobj.area() );
9.            sobj = r1;
10.            sobj.showinfo();
11.            System.out.println ("area = " + sobj.area() );
12.          }
13.        }

程式碼第3行宣告了一個SHAPE型別變數,但由於SHAPE是個抽象類別,不能建構物件實體,所以sobj物件變數參照初始值設定為null。

在物件導向機制中,父類別型別的物件變數允許參照到其子類別的物件實體,在程式碼第6行,sobj參照到CIRCLE類型物件實體c1,所以程式碼第7行sobj.showinfo()表示叫用CIRCLE類別之showinfo()方法,程式碼第8行sobj.area()表示叫用CIRCLE類別之area()方法。

而當程式碼第9行將sobj參照到RECTANGLE類型物件實體r1之後,程式碼第10行sobj.showinfo()表示叫用RECTANGLE類別之showinfo()方法,程式碼第11行sobj.area()表示叫用RECTANGLE類別之area()方法。

換言之,JVM在叫用方法時會依據參照變數實際所參照的物件實體,動態連結到正確的物件方法,這種機制稱為多型性 (polymorphism)。

This is a circle
area = 28.26
This is a rectangle
area = 13.5

漫談物件導向(十) - 封裝性

封裝性 (encapsulation)

封裝性 (encapsulation)是指將物件的資料成員 (data member) 保護或隱藏起來,防止有未授權而任意更改物件內部資料之情形,以免破壞資料完整性,所有內部資料存取只能透過公開的管道 (成員函式,member function)來存取。

一旦物件實體建構完成,就可使用『物件變數.方法名稱()』方式來叫用類別對外公開的方法。對一個公開的資料成員來說,當然也可使用『物件變數.資料成員名稱』來存取,不過將資料成員宣告為public,直接讓外界存取這樣作法並不恰當,嚴重違反物件封裝性原則 (encapsulation),合理方式是將資料成員設定為私有性 (private),只允許透過統一方法或介面實作來存取得類別的內部資料,這樣才能有效管制資料的使用權,維護物件資源的完整性與存取安全。

通常稱呼用來讀存物件內部資料的函式為取值函式 (getter),用來變更物件內部資料的函式為設值函式 (setter),也有些人使用存取子 (accessor) 和更動子 (mutator) 來稱呼它們。

理想的封裝是強調鬆散耦合 (loose coupling) 而避開緊密耦合,追求高內聚力 (high cohesion) 而避開低內聚力,也就是儘量讓類別本身功能完整獨立,與其它類別的相依性愈低愈好。

2011年3月17日 星期四

漫談物件導向(九) - 介面

介面 (Interface)

介面的概念有點類似抽象類別,但介面內純粹只能有常數型別資料成員宣告及介面方法原型宣告 (沒有實作程式碼),所以介面資料成員只能宣告為public、static及final,而介面方法只能宣告為public及abstract。介面定義的語法如下:

public abstract interface interface-name [extends interface1, interface2, …]{
    public static final data-type data- members-name = values;  // 資料成員宣告
    public abstract data-type interface-methods();   // 類別方法宣告
}

下列程式碼宣告了ISHAPE介面

1.   public abstract interface ISHAPE {
2.       public static final float pi = 3.14F;
3.       public abstract float area();
4.       public abstract float perimeter();
5.       public abstract void showinfo();
6.   }

由於介面之定義、介面方法及資料成員宣告方式都已被嚴格規範修飾字,因此,ISHAPE介面也可簡化如下:

1.   interface ISHAPE {
2.       float pi = 3.14F;
3.       float area();
4.       float perimeter();
5.       void showinfo();
6.   }

另外,介面只能使用常數資料成員 (宣告的資料成員預設都會加上final修飾字),因此所有資料成員都必須給定資料值,同時不允許在程式中變更資料值。

介面與抽象類別之間還有一個差異點,介面之間允許多重繼承,但類別之間只能存在單一繼承。類別要實作介面時,必須在類別宣告別使用implements關鍵字,下圖說明類別與介面間的繼承關係。


以下CIRCLE類別實作ISHAPE介面,在CIRCLE類別中並未宣告pi資料成員,但卻可以使用pi,這是因為CIRCLE繼承了ISHAPE介面的pi資料成員。

1.    public class CIRCLE implements ISHAPE {
2.        private float radius;              // 資料成員
3.        public CIRCLE (float r) {   // 建構子
4.        radius = r;
5.        }
6.        public float area() {
7.           return pi*radius*radius;   // 實作圓面積計算
8.        }
9.        public float perimeter() {
10.          return 2*pi*radius;         // 實作圓周長計算
11.     }
12.     public void showinfo() {
13.         System.out.println ("This is a circle");
14.     }
15.  }

2011年3月16日 星期三

漫談物件導向(八) - 重載

重載 (Overloading) 方法

程序式程式語言之副程式 (函式) 名稱必須唯一,當我們要設計計算平均值之副程式,計算整數平均值副程式與計算浮點數平均值副程式就不能使用相同名稱。物件導向程式語言則提供重載方法機制,允許類別中的方法使用同樣名稱,只要這些方法的參數資料型態或參數個數不完全一樣即可,這樣的機制使得程式設計人員能較少苦惱於方法名稱的設計,以統一的名稱來呼叫。方法重載時會自動根據方法參數列的資料型態及參數個數的個數叫用正確的方法。

下列程式碼中,RECTANGLE類別提供了二個建構子,第一個建構子沒有任何引數,第二個建構子則使用了二個浮點類型的引數。

1.    public class RECTANGLE extends SHAPE {
2.        private float width; // 資料成員
3.        private float height;
4.        public RECTANGLE () { // 建構子
5.            height = 2.0F; // 使用預設的高度值
6.            width = 3.0F; // 使用預設的寬度值
7.        }
8.        public RECTANGLE (float h, float w) { // 建構子
9.            height = h;
10.          width = w;
11.      }
12.      public static void main(String[] args) {
13.          RECTANGLE rect1 = new RECTANGLE ();
14.          RECTANGLE rect2 = new RECTANGLE (5.0F, 10.0F);
15.     }
16.  }

對程式碼第13行而言,RECTANGLE建構子並未給定任何引數,因此會使用程式碼第4~7行的建構子。而以程式碼第14行來說,RECTANGLE建構子伴隨二個浮點引數,因此會使用程式碼第8~11行的建構子。

不管是預設建構子RECTANGLE ()或給定長寬引數的建構子RECTANGLE (h, w),其實要做的事情都類似,差別只有height及weight資料成員設定值不同而已,因此,預設建構子可改寫如下:

1.    public RECTANGLE () { // 物件建構函式
2.        this (2.0F, 3.0F);
3.   }

2011年3月10日 星期四

漫談物件導向(七) - this與super

this與super

super關鍵字表示父類別的意思,程式中可以使用super叫用父類別中被子類別覆蓋的變數或方法。下列程式碼中第9行super.showme()表示叫用父類別PARENT中的print()方法。

1.    public class PARENT {
2.        protected void showinfo() {
3.            System.out.println("invoke the method of class PARENT.");
4.        }
5.    }
6.    public class CHILD extends PARENT {
7.        public void showinfo() {
8.            System.out.println("invoke the method of class CHILD.");
9.            super.showinfo(); // 叫用父類別的showme()方法
10.      }
11.      public static void main(String[] args) {
12.          CHILD objDemo = new CHILD();
13.          objDemo.showinfo();
14.      }
15. }

this則代表物件本身,當你想取得對當前物件參照,可使用this關鍵字。下列程式碼第4行this.name = name,第一個name所代表的是DEMO類別中的資料成員,而等號後面的name則是代表setName()方法中的引數名稱。

1.    public class DEMO {
2.        private String name;
3.        public setName(String name) {
4.            this.name = name; //前一個name是DEMO的資料成員; //後一個name是setName()方法的引數
5.        }
6.    }

再者,super/this與super()/this()二者混淆了。super()表示叫用父類別的預設建構子(沒有引數的建構子),this()則是表示叫用自身類別的預設建構子。

還有一點要注意,當Java建立子類別物件時,JVM會先呼叫父類別的預設建構子,之後才繼續執行子類別建構子的程式碼,換言之,在建構子程式碼的第一行隱含了super()敘述。

以下列程式碼為例,類別B繼承類別A,當程式碼第11行執行建構類別B物件實體時,就會進入類別B的建構子 (程式碼第7行),由於存在著繼承關係,JVM先呼叫執行類別A的建構子 (程式碼第2~4行),之後再繼續類別B的建構子程式 (程式碼第8~9行),因此程式執行結果會輸出"AB"。

1.    class A{
2.        public A(){
3.            System.out.print("A");
4.        }
5.    }
6.    class B extends A {
7.        public B(){ // 會先隱含執行super()敘述
8.            System.out.print("B");
9.        }
10.      public static void main(String[] args) {
11.          new B();
12.      }
13.  }

對上述程式碼而言,類別B建構子程式碼相當於:

1.    public B (){
2.        super ();
3.        System.out.print ("B");
4.    }

super()/this()也可加入引數,表示叫用有引數的建構子。最後要強調的是super()或this()均需放在方法的第一行,還有this和super指的都是物件,所以,不可以在static環境中使用,包括static方法及static區塊。