2008年7月15日 星期二

Interface的應用:Dependency Inversion

所謂DIP(Dependency Inversion Principle) 依其定義:
  • 高階模組不應相依於低階模組
  • 高階模組和低階模組皆相依於抽象層
  • 抽象層不應相依於實做細節
  • 實做細節應相依於抽象層
這樣的設計方式,可讓所有物件/模組間的相依關系減至最低,最佳狀況下能夠達成物件鬆藕合的理想。但理想和現實是有差距的,為了達成理想而必須付出難以忍受的代價是不切實際。能夠想像所有的物件定義,皆須有一至多個對應的interface的情況嗎?有誰真能在寫程式時,所有變數皆以抽象層或interface替代?本篇並不是要提倡DIP,甚至在接下來的內容是違反DIP的,若想研究DIP應試著用google key入DIP關鍵字,本篇只在說明如何使用interface達成dependency inversion(相依性反轉)。

所謂Dependency Inversion
假定我們有套件及類別如下:

套件

內含類別

用途

EIP

UpdateUserData

更新使用者資料

EIPLibrary

WriteToDatabase

寫入資料庫


EIP package定位為高階的模組,用於處理使用者相關資料;DbLibrary定位為低階模組,用於協助EIP package將使用者資料寫入資料庫。沒錯,這是典型傳統的結構化程序設計中,高階模組依賴於低階模組的情況,今日我們並沒有打算改變讓DbLibrary相依於EIP,但考慮以下情況:
DbLibrary的部份定義為
class WriteToDatabase {
void WriteNow( UserData obj ) {
WriteName( obj.GetName() );
}
void WriteNow( string userName );
}
由於WriteToDatabase中使用了UserData,所以我們說WriteToDatabase相依於UserData,同時也意味著DbLibrary相依於EIP;但一開始我們已經說明,DbLibrary的目的,是協助EIP,也就是EIP本已相依於DbLibrary,今WriteToDatabase的撰寫方式,豈不是讓WriteToDatabase相依於EIP?循環相依,明顯的是一個設計上的錯誤,同時WriteToDatabase相依於EIP的情況,也讓WriteToDatabase的用途受限(引用WriteToDatabase時,勢必同時要引用EIP package)。有什麼方法可以fix這個窘境?有,答案就是interface,我們可以在package DbLibrary中定義一interface:
interface IUserData {
string GetName();
}

接著讓UserData implement IUserData:
class UserData implements IUserData {
string GetName();
}

再來我們修改WriteToDatabase的定義:
class WriteToDatabase {
void WriteNow( IUserData obj ) {
WriteName( obj.GetName() );
}
void WriteNow( string userName );
}

由於IUserData是定義在DbLibrary中,因此WriteToDatabase並不需要相依於EIP;而UserData因為參考到IUserData,所以反倒是依賴於DbLibrary了,如此即反轉原本程式的相依方向了。

沒有留言: