首先,為什麼要用DIP呢?
書上舉了一個很好玩的例子來說明,叫"Hollywood Principle"
意思是"Don't Call me, I'll Call you"
好萊塢的經紀人不希望面試者主動聯絡他們,而是當他們決定要用你的時候才會主動聯絡。
以程式來說,就是讓物件自己決定要如何呼叫其他物件,而不是依賴於其他物件。
假設說今天我們要設計一個可以讀取Book資料的Reader
class Reader{ private Book book = new Book("Harry Potter"); public void read(int page){ book.read(page); } }
Reader依賴於book這個class的函式,
所以當我們要為這個程式增加可以閱讀EBook功能的時候,我們必須加入邏輯判斷來區別
第一個想到的就是新增TypeCode判斷,
於是醜陋的程式碼就產生了...
class Reader{ static final int BOOK = 1,EBOOK = 2; public void read(int page, int bookType){ if(bookType = BOOK){ Book book = new Book("Harry Potter"); book.read(page); }else if(bookType = EBOOK){ EBook book = new EBook("Harry Potter"); book.read(page); } } }
使用Dependency Injection Pattern,能使Reader這個Class不會被book這個class綁死,
相反的,Reader可以先宣告自己會使用的介面,等於是告訴其他Class
"我會用這個方式來呼叫你,如果你想讓我用的話,就先實做這個介面":
class Reader{ private IBook book; public void read(int page){ book.read(page); } public Reader(IBook book){ this.book = book; } } class Book implement IBook{...} class EBook implement IBook{...}
使用介面,或是abstract class來宣告使用的介面,
讓Reader這個class可以不被單一的Book class所綁死
只要是實作IBook這個介面的Class,Reader都可以使用
public Reader(IBook book){ this.book = book; }
而這段程式碼在呼叫Reader的建構式時傳入它要使用的Book
這就是Dependency Injection(控制反轉)的一種
是當你要使用物件的時候才傳入物件之間的依賴關係,而不是在一開始就在程式碼中指定
這樣可以保持物件之間彼此的鬆散偶合,增加擴充性.
另外,在作單元測試時,DI也是很有用的一個Pattern
如果我們要測試Reader的Read()這個函式,但是Read這個函式又會呼叫到Book,
然後Book會連接到背後的資料物件...這樣我們無法知道說測試的成功或失敗,
是因為Reader的關係,還是Book,或是背後的資料庫的問題.
這個時候我們就可以使用Dependency Injection的特性,將Book與Reader之間的關係給切開,
先寫一個測試用的Book,實作IBook的介面,並且紀錄是否被呼叫:
public class TestBook implement IBook(){ public bool readed = false; public void read(int page){ readed = true; } } //測試案例: public void ReaderTest(){ TestBook book = new TestBook() Reader reader = new Reader(book); reader.read(); assertTrue(book.readed); }
利用這個方式,我們可以只測試我們想測試的邏輯.
也可以寫一個假的Data Access Class,將資料庫的部分先抽換掉,
來測試系統的介面與流程是否ok.
more:
Martin Flower提出的DI概念
良葛格對DI的詳細說明
No comments:
Post a Comment