黑馬程式設計師-Java中面向抽象和麵向介面設計
一、“開閉原則”
在討論面向抽象和麵向介面之前,先來粗略瞭解下“開閉原則”。
最近在搜尋問題的時候總是會看到有人提到“開閉原則”的設計思想。今天就稍微總結下JavaSE中最能體現”開閉”思想的兩部分。我覺得在學習早期或多或少的滲透理解一些思想,這種潛移默化的影響對於以後形成強大獨到的設計思維體系大有益處。所以先來看下“開閉原則“。
一個好的軟體的設計,在於更加容易被複用或維護。當修改某部分時,不會對其它部分造成影響。
“開閉原則”正是面向物件程式語言的可複用設計的基石,其它的模式如里氏替換、依賴注入、迪米特原則都是開閉原則的具體實現工具。
1.定義:一個程式應當對擴充套件開放,而對修改關閉。
2.好處
(1)在對程式擴充套件之後,程式擁有了新的功能,使得程式不會因為需求的變化而淘汰。
(2)程式不能再被修改,即自身高度提取的抽象層不能被隨意修改,體現了程式的穩定性。
3.實現原則
在開閉原則的設計中,首先要有“閉”的東西才能”開”才能被擴充套件,所以什麼東西需要關閉是關鍵。
一般在面向物件設計過程中,我們要把物件的共性特徵提取出來抽象化,讓具體細節由子類實現。這樣我們在抽象層面考慮問題就簡單化,統一化了。
當把最本質的共性特徵提取後,自然就想到了用抽象類和介面來關閉它。
二、面向抽象設計
1.回顧抽象類
抽象類用abstract關鍵字來宣告,抽象類中可以包含抽象方法,抽象方法是隻有方法宣告部分的特殊方法,用abstract來修飾,通過子類的重寫使用。除此之外,抽象類和普通類相同。
2.面向抽象
比如我們要設計一個用來計算幾何柱體體積的類:
public class Size{
private double floor=0;//底面積
private double high=0;//高
public Size(double high){
this.high=high;
}
//獲取體積的方法
public double getSize(){
return floor*high;
}
//設定三角稜柱底面積
public void setTrigonFloor(double l,double h){
floor = l*h/2.0;
}
//設定圓柱底面積
public void double setCircleFloor(double r){
floor = 3.14*r*r;
}
}
在本類中可以計算圓柱體的體積,三稜柱的體積,但是計算長方體等其他的就需要修改其中的獲取底面積的方法了,這顯然是不利於程式碼維護的。所以我們想到用“開閉”原則去重新設計。
面向抽象第一步:抽象細節
抽象細節就是把變化的,有複雜的實現細節的部分分割出來,將其提取成抽象方法,而不必關心實現的細節。
我們發現計算柱體體積是需要高和底面積,而高是不變的,變化的是底面積的獲取方法。所以要考慮把獲取底面積的方法抽象化,設計成類。
public abstract class Floor{
//返回幾何圖形的面積
public abstract double getFloor();
}
這樣就完成了第一步。
面向抽象第二步:面向抽象設計類
面向第一步設計的抽象類,要使用它,就應該面向它來設計一個新類,把它作為成員。如下:
public class Size{
//底面積類
private Floor floor;
//柱體的高
private double high;
Size(Floor floor,double high){
this.floor=floor;
this.high=high;
}
//計算柱體的體積
public double getSize(){
return floor.getFloor()*high;
}
}
接下來要使用它就可以通過擴充套件Floor抽象類來實現。
//這是一個計算三角形面積的實現類
public class Triangle extends Floor{
//三角形的低和高
double i,h;
Triangle(double i,double h){
this.i=i;
this.h=h;
}
//重寫的方法,獲取三角形面積
public double getFloor(){
return i*h/2;
}
}
//這是一個計算長方形面積的實現類
public class Cuboid extends Floor{
//長方形的長和寬
double l,k;
Cuboid(double l,double k){
this.l=l;
this.k=k;
}
//重寫的方法,獲取長方形的面積
public double getFloor(){
return l*k;
}
}
下面我們就可以使用了:
public class Test{
public static void main(String[] args){
// 宣告Size類變數
Size size;
// 宣告Floor抽象類變數
Floor floor;
// 建立長方形面積物件並用floor例項化
floor = new Cuboid(52.2, 23.3);
// 例項化size變數並用floor初始化
size = new Size(floor, 10);
// 列印長方體體積的計算結果
System.out.println("長方體的體積是:" + size.getSize());
// 建立三角形面積物件並用floor例項化
floor = new Triangle(20, 58.5);
// 例項化size變數並用floor初始化
size = new Size(floor, 45);
// 列印正三稜柱體積的計算結果
System.out.println("三稜體的體積是:" + s ize.getSize());
}
}
總結:面向抽象是把程式中變化的部分抽象提取設計成抽象類,這一步即為“開閉原則”中的“閉”,該抽象類為所有變化部分的共同特性,不可改變。通過擴充套件抽象類,達到不同的實現,這一步為“開閉原則”中的“開”。最後把抽象類宣告為其三、面向介面設計抽象。
三、面向介面設計
1.介面的回顧
(1)介面通過interface 來宣告。
(2)介面中方法預設是public abstract。
(3)介面中只能有抽象方法和常量(Java 8 後可以使用default關鍵字宣告預設方法)。
2.介面回撥
介面回撥是多型的一種體現。通過介面型別變數來接收介面實現類物件,用介面變數呼叫被實現的方法,這時候是相應的子類物件在呼叫介面方法,這一過程成為介面回撥。
3.面向介面
依然採用上面的例子來說明。
這時我們把求面積方法定義為一個介面,面向該介面設計類。當程式需要新增新的型別時候,只需要再增加一個新的介面的實現類即可。
//把底面積宣告成介面
interface Floor{
double getFloor();
}
宣告實現類:
//宣告長方形面積實現類
class Cuboid implements Floor{
double l,k;
Cuboid(double l,double k){
this.l=l;
this.k=k;
}
public double getFloor(){
return l*k;
}
}
//宣告三角行面積實現類
class Triangle extends Floor{
// 三角形的低和高double i, h;
Triangle(double i, double h) {
this.i = i;
this.h = h;
}
// 獲取三角形面積
public double getFloor() {
return i * h / 2;
}
}
測試:
public class Test {
public static void main(String[] args) {
// 宣告Size類變數
Size size;
// 宣告Floor抽象類變數
Floor floor;
// 建立長方形面積物件並用floor例項化
floor = new Cuboid(52.2, 23.3);
// 例項化size變數並用floor初始化
size = new Size(floor, 10);
// 列印長方體體積的計算結果
System.out.println("長方體的體積是:" + size.getSize());
// 建立三角形面積物件並用floor例項化
floor = new Triangle(20, 58.5);
// 例項化size變數並用floor初始化
size = new Size(floor, 45);
// 列印正三稜柱體積的計算結果
System.out.println("三稜體的體積是:" + size.getSize());
}
}
總結:面向介面和麵向抽象實現原理都是一樣的。
四、介面與抽象類的使用環境
1.抽象類可以包含抽象方法,非抽象方法,成員變數,所以它是對多個類進行提取抽象。它隱含著是什麼的關係。
2.介面中只能有抽象方法和常量,所以它是比抽象類更高度抽象的。它是對多個功能進行提取抽象,它隱含的是有和沒有該功能的關係。