上一篇文章提到依賴,依賴也無所不在,而 DIP 要我們將依賴做「反轉」,是要反轉什麼?反過來依賴嗎? 當然不是,而是解除直接依賴的關係轉而去依賴於抽象介面
,使得位於較低層次的類別依賴關係的方向反了過來,大致上看起來就會像這樣。
我們不會沒事去依賴其他類別,一定是該類別有提供我們需要的方法才會去把它建立起來使用,而就在建立
並使用
(Create and Use)的過程產生了依賴,DIP 要我們不要直接依賴,中間應該墊一個抽象介面。
利用上一篇文章提到的範例:
internal class Program
{
private static readonly string ConnectionString = "...";
private static void Main(string[] args)
{
var conn = new SqlConnection(ConnectionString);
conn.Open();
}
}
我們先從「使用
」這個部分下手解除直接依賴關係,轉依賴抽象介面,將 conn 的型別改為 IDbConnection
就可以看見 Open() 方法就不再是從 SqlConnection 具體類別參考來的。
再來我們從「建立
」這個部分來解除直接依賴關係,在這邊我們要有一個觀念「建立是必要的,沒有建立,物件就不能使用。」,不管我們是使用 DI Framework 或是套工廠模式...等方式建立物件,都只是將建立物件的職責移到其他地方,還是免不了會在建立物件的地方產生直接依賴,這裡我選用簡單工廠方法來處理建立物件的部分。
internal class Program
{
private static readonly string ConnectionString = "...";
private static void Main(string[] args)
{
IDbConnection conn = DbConnectionFactory.Create("SQL Server", ConnectionString);
conn.Open();
}
}
internal static class DbConnectionFactory
{
public static IDbConnection Create(string kindOfDb, string connectionString)
{
switch (kindOfDb.ToLowerInvariant())
{
case "sql server": return new SqlConnection(connectionString);
case "oracle": return new OracleConnection(connectionString);
default: throw new ArgumentOutOfRangeException(nameof(kindOfDb));
}
}
}
這樣子 Program 類別與 SqlConnection 類別就解除了直接依賴關係,轉而去依賴 IDbConnection,而我們為什麼要做這樣的事?-「為了提高高層次類別重複運用的容易程度
」
SqlConnection 類別是與 SQL Server 建立連線用的,當我的資料庫換成了 Oracle,與之連線用的是 OracleConnection,如果 SqlConnection 與 OracleConnection 沒有相同的抽象介面規範,都自己搞自己的,開啟連線就有可能一個叫 Open()、一個叫 Connect(),如果真是這樣就遑論其他 API 了,程式要從 SQL Server 搬遷到 Oracle 的話就很辛苦了,兩邊 API 不一樣的地方都要一行一行地改。
如果 SqlConnection 與 OracleConnection 都有實作同一個介面(事實上有,就是 IDbConnection。),只要調整一下丟給 DbConnectionFactory.Create() 方法的參數及連線字串,這樣子切換不同資料庫就顯得容易多了,其他的 API 如果也都有實作相同介面(事實上也有),那麼原先已經寫好的商業邏輯,重複運用的容易程度就大大地提升了。
如果有用 Dapper 的朋友可以隨便挑一個方法「移至定義」看一下,它的擴充方法是擴充在 IDbConnection 上,這也就是為什麼 Dapper 可以支援多種的資料庫。
實務上或許很少遇到這種轉換資料庫的情境,但是使用的框架/Library 面臨 phase out 需要升級或是換另外一套也是類似的,在這樣的情況之下,要是商業邏輯的程式碼直接依賴框架/Library 的話,就痛苦了,因此如果我們把商業邏輯的部分看成高層模組,把框架/Library 看成低層模組,中間墊一層自己設計
的抽象介面的話,要升級或抽換框架/Library 相對就容易多了。
事實上這也是我目前開發程式的起手式,甚至是 .NET Framework 所提供的 API 我也先視為是低層模組,因為框架/Library 像四季一樣會更迭,商業邏輯則是像文化一樣會延續,好了,反轉就談到這裡,希望這兩篇文章對 DIP 的了解有幫助。