原創 以下內容來自《Java 2實用教程》,主編:耿祥義、張躍平 鑒於面向抽象編程和麵向介面編程思維培養的重要性,寫此博客鞏固。 面向抽象編程: 在設計程式時,經常會使用到abstract類,其原因是,abstract類只關心操作,而不關心這些操作具體的實現細節, 可以使程式的設計者把主要精力放在程 ...
原創
以下內容來自《Java 2實用教程》,主編:耿祥義、張躍平
鑒於面向抽象編程和麵向介面編程思維培養的重要性,寫此博客鞏固。
面向抽象編程:
在設計程式時,經常會使用到abstract類,其原因是,abstract類只關心操作,而不關心這些操作具體的實現細節,
可以使程式的設計者把主要精力放在程式的設計上,而不必拘泥於細節的實現(將這些細節留給子類的設計者),即避免
設計者把大量的時間和精力花費在具體的演算法上。例如,在設計地圖時,首先考慮地圖最重要的輪廓,不必去考慮諸如城
市中的街道牌號等細節,細節應當由抽象類的非抽象子類去實現,這些子類可以給出具體的實例,來完成程式功能的具體
實現。在設計一個程式時,可以通過在abstract類中聲明若幹個abstract方法,表明這些方法在整個系統設計中的重要性,
方法體的內容細節由它的非abstract子類去完成。
使用多態進行程式設計的核心技術之一是使用上轉型對象,即將abstract類聲明的對象作為其子類對象的上轉型對象,
那麼這個上轉型對象就可以調用子類重寫的方法。
所謂面向抽象編程,是指當設計某種重要的類時,不讓該類面向具體的類,而是面向抽象類,即所設計類中的重要數據
是抽象類聲明的對象,而不是具體類的聲明的對象。
以下通過一個簡單的問題來說明面向抽象編程的思想。
例如,我們已經有了一個Circle類(圓類),該類創建的對象Circle調用getArea()方法可以計算圓的面積。Circle類
的代碼如下:
Circle.java
1 public class Circle { 2 double r; 3 Circle(double r){ 4 this.r=r; 5 } 6 public double getArea() { 7 return(3.14*r*r); 8 } 9 }
現在要設計一個Pillar類(柱類),該類的對象調用getVolume()方法可以計算柱體的體積。Pillar類的代碼如下:
Pillar.java
1 public class Pillar { 2 Circle bottom; //bottom是用具體類Circle聲明的對象 3 double height; 4 Pillar (Circle bottom,double height){ 5 this.bottom=bottom; 6 this.height=height; 7 } 8 public double getVolume() { 9 return bottom.getArea()*height; 10 } 11 }
上述Pillar類中,bottom是用具體類Circle聲明的對象,如果不涉及用戶需求的變化,上面Pillar類的設計沒有什麼不妥,
但是在某個時候,用戶希望Pillar類能創建出底是三角形的柱體。顯然上述Pillar類無法創建出這樣的柱體,即上述設計的Pillar
類不能應對用戶的這種需求(軟體設計面臨的最大問題是用戶需求的變化)。我們發現,用戶需求的柱體的底無論是何種圖形,但
有一點是相同的,即要求該圖形必須有計算面積的行為,因此可以用一個抽象類封裝這個行為標準:在抽象類里定義一個抽象方法
abstract double getArea(),即用抽象類封裝許多子類都必有的行為。
現在我們來重新設計Pillar類。首先,我們註意到柱體計算體積的關鍵在計算出底面積,一個柱體在計算底面積時不應該關心
它的底是什麼形狀的具體圖形,只應該關心這種圖形是否具有計算面積的方法。因此,在設計Pillar類時不應該讓它的底是某個具體
類聲明的對象,一旦這樣做,Pillar類就依賴該具體類,缺乏彈性,難以應對需求的變化。
下麵我們將面對抽象重新設計Pillar類。首先編寫一個抽象類Geometry,該抽象類中定義了一個抽象的getArea()方法。
Geometry類如下:
Geometry.java
1 public abstract class Geometry { 2 public abstract double getArea(); 3 }
上述抽象類將所有計算面積的演算法抽象為一個標識:getArea(),即抽象方法,不用考慮演算法的細節。
現在Pillar類的設計者可以面向Geometry類編寫代碼,即Pillar類應該把Geometry對象作為自己的成員,該成員可以調用Geometry
的子類重寫的getArea()方法。這樣一來,Pillar類就可以將計算底面積的任務指派給Geometry類的子類的實例(用戶的各種需求將由
不同的子類去負責)。
以下Pillar類的設計不再依賴具體類,而是面向Geometry類,即Pillar類中的bottom是用抽象類Geometry聲明的對象,而不是具體類
聲明的對象。重新設計的Pillar類的代碼如下:
Pillar.java
1 public class Pillar { 2 Geometry bottom; //bottom是抽象類Geometry聲明的變數 3 double height; 4 Pillar (Geometry bottom,double height){ 5 this.bottom=bottom; 6 this.height=height; 7 } 8 public double getVolume() { 9 if(bottom==null) { 10 System.out.println("沒有底,無法計算體積"); 11 return -1; 12 } 13 return bottom.getArea()*height; //bottom可以調用子類重寫的getArea方法 14 } 15 }
下列Circle和Rectangle類都是Geometry的子類,二者都必須重寫Geometry類和getArea()方法來計算各自的面積。
Circle.java
1 public class Circle extends Geometry{ 2 double r; 3 Circle(double r){ 4 this.r=r; 5 } 6 public double getArea() { 7 return(3.14*r*r); 8 } 9 }
Rectangle.java
1 public class Rectangle { 2 double a,b; 3 Rectangle(double a,double b){ 4 this.a=a; 5 this.b=b; 6 } 7 public double getArea() { 8 return a*b; 9 } 10 }
註意到,當增加了Circle和Recangle類後,我們不必修改Pillar類的代碼。現在,我們就可以用Pillar類創建
出具有矩形底或圓形底的柱體了,如下列Application.java所示,程式運行效果如圖5.13所示。
Application.java
1 public class Application { 2 public static void main(String args[]) { 3 Pillar pillar; 4 Geometry bottom=null; 5 pillar = new Pillar(bottom,100); //null底的柱體 6 System.out.println("體積"+pillar.getVolume()); 7 bottom=new Rectangle(12,22); 8 pillar=new Pillar(bottom,58); //pillar是具有矩形底的柱體 9 System.out.println("體積"+pillar.getVolume()); 10 bottom=new Circle(10); 11 pillar =new Pillar (bottom,58); //pillar是具有圓形底的柱體 12 System.out.println("體積"+pillar.getVolume()); 13 } 14 }
通過面向抽象類設計Pillar類,使得該Pillar類不再依賴具體類,因此每當系統增加新的Geometry的子類時,
例如增加一個Triangele子類,那麼我們不需要修改Pillar類的任何代碼,就可以使用Pillar創建出具有三角形底
的柱體。
通過前面的討論我們可以做出如下總結:
面向抽象編程的目的是為了應對用戶需求的變化,將某個類中經常因需求變化而需要改變的代碼從該類中分離
出去。面向抽象編程的核心是讓類中每種可能的變化對應地交給抽象類的一個子類去負責,從而讓該類的設計者不
去關心具體實現,避免所設計的類依賴於具體的實現。面向抽象編程使設計的類容易應對用戶需求的變化。
面向介面編程:
抽象類最本質的特性是可以包含抽象方法,這一點和介面類似,只不過介面中只有抽象方法而已。抽象類將其抽象方法
的實現交給其子類,而介面將其抽象方法的實現交給實現該介面的類。在設計程式時,學習怎樣面向介面去設計程式。介面
只關心操作,但不關心這些操作的具體實現細節,可以使我們把主要精力放在程式的設計上,而不必拘泥於細節的實現。也
就是說,可以通過在介面中聲明若幹個abstract方法,表明這些方法的重要性,方法體的內容細節由實現介面的類去完成。
使用介面進行程式設計的核心思想是使用介面回調,即介面變數存放實現該介面的類的對象的引用,從而介面變數就可以回
調類實現的介面方法。利用介面也可以體現程式設計的“開-閉原則”,即對擴展開放,對修改關閉。例如,程式的主要設計
者可以設計出如下圖所示的一種結構關係。
從下圖可以看出,當程式再增加實現介面的類(由其他設計者去實現),介面變數variable所在的類不需要做任何修改,
就可以回調類重寫的介面方法。
當然,在程式設計好後,首先應當對介面的修改“關閉”,否則,一旦修改介面,例如,為它增加一個abstract方法,
那麼實現該介面的類都需要作出修改。但是,程式設計好後,應當對增加實現介面的類“開放”,即在程式中再增加實現
介面的類時,不需要修改其他重要的類。
個人的一點小薄見:
面向抽象編程和麵向介面編程的思路都是一樣的,面向抽象編程依靠上轉型對象來實現;面向介面編程依靠介面回調
來實現;這種思想對於軟體設計十分重要。Java中的一大法寶是多態,多態分兩種,其中一種就是通過繼承來實現的,子
類通過定義自己的行為來“展示自己”,每個子類都有不同的行為,所以展現出多態性。而我們可以建立一個類,這個類
可以幫助“每一個子類”來展現他們自己,而不用他們自己親自動手,這會大大縮減程式的代碼長度,假如有100個子類,
如果要求每一個子類都親自展現自己,必須定義100段不同的代碼;而通過一個類輪流為它們服務,這會顯得更方便,這
楊的一個幫助子類展現自己的類被稱為面向抽象的類(介面也一樣),面向抽象的類為子類實例的上轉型對象,通過調用
子類重寫的方法來實現多態。
19:17:05
2018-07-07