Java設計模式——依賴倒轉原則

一、什麼是依賴倒轉原則?

依賴倒轉原則講的是,要依賴於抽象,不要依賴於具體

實現“開-閉”原則的關鍵是抽象化,並且從抽象化導出具體化實現。“開-閉”原則是面向對象設計的目標,依賴倒轉原則是面向對象設計的主要機制。

依賴倒轉原則的另一種表述,要針對接口編程,不要針對實現編程。

針對接口編程是說,應該使用Java接口或Java抽象類進行變量的類型聲明、參量的類型聲明、方法的返回類型聲明,以及數據類型的轉換等。

不要針對實現編程是說,不應當使用具體Java類進行變量的類型聲明、參量的類型聲明、方法的返回類型聲明,以及數據類型的轉換等。

要保證做到這一點,一個具體Java類應當只實現Java接口或Java抽象類中聲明過的方法,而不應當給出多餘的方法。

二、怎樣做到依賴倒轉原則

以抽象方式耦合是依賴倒轉原則的關鍵。由於一個抽象耦合關係總要涉及到具體類從抽象類繼承,並且需要保證在任何引用到基類的地方都可以改換成子類,因此,里氏代換原則是依賴倒轉原則的基礎

在抽象層次上的耦合雖然具有靈活性,但也帶來了額外的複雜性。在某些情況下,如果一個具體類發生變化的可能性非常小,那麼抽象耦合能發揮的好處便十分有限,這時使用具體耦合反而會更好。

依賴倒轉原則是面向對象設計的核心原則,設計模式的研究和應用是以依賴倒轉原則爲指導原則的。

三種耦合關係

在面向對象的系統裏,兩個類之間可以發生三種不同的耦合關係:

l  零耦合關係:如果兩個類沒有耦合關係,則稱爲零耦合。

l  具體耦合關係:具體耦合發生在兩個具體的類(可實例化的類)之間,經由一個類對另個一具體類的直接引用形成。

l  抽象耦合關係:抽象耦合關係發生在一個具體類和一個抽象類(或者Java接口)之間,使兩個必須發生關係的類之間存有較大的靈活性。

變量的靜態類型和真實類型

變量聲明時的類型叫作變量的靜態類型(Static Type),變量所引用的對象的實際類型叫作變量的真實類型(Actual Type)。比如:

Listemployees=new Vector();

其中,List是變量employees的靜態類型,而它的實際類型是Vector。

三、Java對抽象類型的支持

在Java語言中,可以定義一種抽象類型,並且提供這一抽象類型的各種具體實現。Java語言提供了兩種機制,Java接口和Java抽象類。

Java接口和Java抽象類的區別和特點

(1)      兩者最明顯的區別,在於Java抽象類可以提供某些方法的部分實現,而Java接口則不可以。這大概是Java抽象類唯一的優點。

如果向一個抽象類加入一個新的具體方法,那麼所有的子類型一下子就得到了這個新的具體方法,而Java接口做不到這一點。如果向Java接口加入一個新的方法的話,所有實現這個接口的類就不能全部成功的通過編譯,因爲他們沒有實現這個新聲明的方法。這是Java接口的一個缺點。

(2)      一個抽象類的實現只能由這個抽象類的子類給出,也就是說,這個實現類處在抽象類所定義出的繼承的等級結構中,而由於Java語言限制一個類只能從最多一個超類繼承,因此將抽象類作爲類型定義工具的效能大大折扣。

對於Java接口,一個實現了一個Java接口所規定的方法的類都可以具有這個接口的類型,而一個類可以實現任意多個Java接口。這是Java接口和Java抽象類最重要的區別。此外,Java接口還具有其他的優越

性。

(3)      從代碼重構的角度上講,將一個單獨的Java具體類重構成一個Java接口的實現是很容易的。只需要聲明一個Java接口,並將重要的方法添加到接口聲明中,然後在具體類定義語句後面加上一個合適的implements子句即可。

而爲一個已有的具體類添加一個Java抽象類作爲抽象類型卻不容易,因爲這個具體類可能已經有一個超類。這樣一來,這個新定義的抽象類只好繼續向上移動,變成這個超類的超類,如此循環,最後這個新定義的抽象類必定處於整個類型等級結構的最上端,從而使等級結構中的所有成員都受到影響。

(4)      Java接口是定義混合類型的理想工具。所謂混合類型,就是在一個類的主要類型之外的次要類型。一個混合類型表明一個類不僅僅具有某個主類型的行爲,而且具有其他的次要行爲。比如HashTable類就具有多個類型。它的主要類型是Map,這是一種Java聚集。而Cloneable接口和Serializable接口就是次要類型。

 

聯合使用Java接口和Java抽象類

由於Java抽象類具有提供缺省實現的優點,而Java接口具有其他的所有優點,所有聯合使用兩者就是一個很好的選擇。

首先,聲明類型的工作仍然是由Java接口承擔的,但是同時給出的還有一個Java抽象類,爲這個接口給出一個缺省實現。其他同屬於這個抽象類型的具體類可以有選擇的實現這個Java接口,也可以選擇繼承自這個抽象類。

如果一個具體類直接實現這個Java接口的話,它就必須自行實現所有的接口;相反,如果一個具體類繼承自Java抽象類的話,它就可以省去一些不必要的方法,因爲它可以從抽象類中自動得到這些方法的缺省實現。

如果需要向Java接口加入一個新的方法的話,那麼只要同時向這個抽象類加入這個方法的一個具體實現就可以了,因爲所有繼承自這個抽象類的子類都會從這個抽象類得到這個具體方法。

這其實就是缺省適配模式。在Java語言API中也是用了這種缺省適配模式,而且全都遵循一定的命名規範:Abstract+接口名。如接口Collection,抽象類名字是AbstractCollection;Map和AbstractMap,List與AbstractList等。

四、一個例子:賬號、賬號的種類和賬號的狀態

Account類有兩個聚合關係,一個是AccountType,另一個是AccountStatus。這兩個類型都是抽象類型,每一個抽象類型都有多於一個的具體實現。比如AccountType有Saving和Checking兩個具體子類;而AccountStatus有Open和Overdrawn兩種具體實現。類圖如下。

 

Account類源代碼。

public class Account {
	private AccountType accountType;
	private AccountStatus accountStatus;
	public Account(AccountType accountType) {
		//code
	}
	public void deposit(float amt) {
		//code
	}
}

抽象類AccountType定義出所有具體子類必須實現的接口。

public abstract class AccountType {
	public abstract void deposit(float amt);
}

抽象類AccountStatus定義出所有具體子類必須實現的接口。

public abstract class AccountStatus {
	public abstract void sendCorrespondence();
}

Saving類是AccountType的具體子類,代表儲蓄賬號。

public class Saving extends AccountType{
	@Override
	public void deposit(float amt) {
		//code
	}
}

Checking類是AccountType的具體子類,代表支票賬號。

public class Checking extends AccountType{
	@Override
	public void deposit(float amt) {
		//code
	}
}

Open是AccountStatus的具體子類,表示賬號處於“開”狀態。

public class Open extends AccountStatus{
	@Override
	public void sendCorrespondence() {
		//code
	}
}

Overdrawn是AccountStatus的具體子類,表示賬號處於“超支”狀態。

public class Overdrawn extends AccountStatus{
	@Override
	public void sendCorrespondence() {
		//code
	}
}

Account類依賴於AccountType和AccountStatus兩個抽象類型,並不依賴於具體類,因此當由新的具體類型添加到系統中時,Account類不用改變。例如,如果系統引進了一種新型賬號:MoneyMarket類型,Account類以及系統裏面所有其他的依賴於AccountType抽象類的客戶端都不需要改變。

MoneyMarket類的源代碼。

public class MoneyMarket extends AccountType{
	@Override
	public void deposit(float amt) {
		//code
	}
}

注意:爲了創建一個實例,必須直接調用此具體類的構造子。如果需要將一個具體類替換成爲另一個具體類,而不改變創建此實例的方法,只有一個方法,那就是將創建責任下推給一個工廠類。

五、依賴倒轉原則的優缺點

依賴倒轉原則雖然很強大,但卻是最不容易實現的。因爲依賴關係倒轉的緣故,對象的創建很可能要使用對象工廠,以避免對具體類的直接引用,此原則的使用還會導致大量的類。

此外,依賴倒轉原則假定所有的具體類都是會變化的,這也不總是正確的。有一些具體類可能是相當穩定,不會發生變化的,消費這個具體類實例的客戶端完全可以依賴於這個具體類型,而不必爲此發明一個抽象類型。

 

發佈了11 篇原創文章 · 獲贊 10 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章