本文中,我們將簡(jiǎn)要介紹一下在應用Martin Fowler所推薦的rich domain model模型中遇到的依賴(lài)注射的問(wèn)題。然后在本文后半部分我們給出一個(gè)相對比較簡(jiǎn)單的利用Yan容器來(lái)管理這些域對象的依賴(lài)的方法。這個(gè)方法不要求程序對容器有任何直接或者間接的依賴(lài),如果愿意,我們完全可以用手工注射作為一個(gè)實(shí)現策略。(當然,它要求容器有足夠的靈活性來(lái)處理這種依賴(lài)管理。)
然后,在下一章Transparent Dependency Injection for Rich Domain Model中,我們介紹Yan提供的一種結合動(dòng)態(tài)代理的更加方便的解決方案。
在討論domain model的時(shí)候,我驚訝地發(fā)現,一些同志在應用馬丁同學(xué)的充血模型(hyperaemia)的時(shí)候,為如何給這些從hibernate創(chuàng )建而來(lái)的domain對象怎么注射依賴(lài)而煩惱.
hibernate或者Dao,不可能知道你的充血對象都需要什么外部的依賴(lài)。對hibernate來(lái)說(shuō),不管你對象是否充血,它都是當作一個(gè)簡(jiǎn)單的結構來(lái)往里填入數據成員。這其實(shí)很尷尬,尤其是對Dao,它將返回一個(gè)半成品對象,這個(gè)對象只有一些數據成員被設置,而其它的對這個(gè)對象的功能至關(guān)重要的依賴(lài)卻還沒(méi)有被建立。這是什么樣的一個(gè)Dao阿。返回一個(gè) "小心!它看起來(lái)是BankAccount,但是千萬(wàn)別當作BankAccount用呢先,你得先設置各種依賴(lài)關(guān)系。" 。
我發(fā)現我怎么就無(wú)論如何喜歡不起來(lái)這種充血模型呢?一個(gè)好的設計,應該盡量用靜態(tài)類(lèi)型來(lái)表示設計意圖。盡量讓符合類(lèi)型要求的操作合法化,而不符合類(lèi)型要求的操作非法。在這個(gè)充血模型里面,事情卻不是這么簡(jiǎn)單。從Dao返回出來(lái)的BankAccount不是真正的BankAccount.
更優(yōu)雅的設計,在我看來(lái),應該是Dao只返回純數據的結構,比如BankAccountData,這個(gè)對象里除了getter/setter就沒(méi)有業(yè)務(wù)邏輯。然后在service層面把BankAccountData轉換為BankAccount。這樣,就不存在一個(gè)半成品的狀態(tài),不存在假象。類(lèi)型告訴我們什么可用,什么就必然可用。
當然,這也許比較麻煩,畢竟多了一個(gè)轉換的步驟,多了BankAccountData這個(gè)類(lèi)型。
好了,不唧唧歪歪了。讓我們看看同學(xué)們頭疼的注射依賴(lài)問(wèn)題。
目前,包括xiecc同學(xué)在內的許多朋友都不約而同地選擇用aop來(lái)完成這個(gè)依賴(lài)注射,我們不用這種手工注射:
class BankAccountService{private final SmtpService smtp;private final BankAccountDao dao;BankAccount getBankAccountById(String id){BankAccount acc = dao.getById(id);acc.setSmtpService(smtp);return acc;}}而是:
class BankAccountService{private final SmtpService smtp;private final BankAccountDao dao;BankAccount getBankAccountById(String id){BankAccount acc = dao.getById(id);//acc.setSmtpService(smtp); return acc;}}簡(jiǎn)單地把acc.setSmtpService給注釋掉。這段代碼雖然簡(jiǎn)短,看起來(lái)很費解,因為明明smtpService沒(méi)有被設置,這個(gè)BankAccount仍然還是個(gè)半成品嘛。
然后,弄一個(gè)aspect,讓它偷偷把smtpService注射給BankAccount。
什么?aspect怎么知道smtpService?當然是service locator了。通過(guò)主動(dòng)在容器里查詢(xún),找到smtpService不就好了?
我來(lái)給這個(gè)方法挑挑毛?。?/p>
那么,我們推薦什么樣的方法呢?
怎么辦?還是ioc。任何時(shí)候,ioc都是我們堅持的原則。
仔細分析需求,我們這里不是要尋找容器,也不是要個(gè)aop,真正最根本的需要是“依賴(lài)注射”。
根據我們面向接口編程的原則,任何時(shí)候,如果需要某個(gè)外界提供的功能,用接口把它描述出來(lái),然后讓外界注射進(jìn)來(lái)。當然,我們這里需要從外界注射的是一個(gè)“依賴(lài)注射邏輯”。有點(diǎn)繞是么?呵呵,看看代碼把:
public interface Injector{void inject(Object obj);}class BankAccountService{private final Injector injector;private final BankAccountDao dao;BankAccountService(Injector injector, BankAccountDao dao){this.injector = injector;this.dao = dao;}BankAccount findAccountById(String id){BankAccount acc = dao.getById(id);injector.inject(acc);return acc;}}
好了,domain object和service的設計到此為止。剩下的就是衣來(lái)伸手,飯來(lái)張口,等著(zhù)外面給我注射這個(gè)我自家用的注射器了。
這個(gè)代碼完全是單體測試友好的。因為我甚至可以簡(jiǎn)單地用手工注射來(lái)實(shí)現Injector。比如:
Injector manual_injector = new Injector(){public Object inject(Object obj){BankAccount acc = (BankAccount)obj;acc.setSmtpService(new SmtpService());}};
閱讀起來(lái),這個(gè)代碼也是自我解釋的。injector.inject(acc)負責注射依賴(lài),于是然后BankAccount就是一個(gè)完整的rich domain object了。邏輯清晰直白。
它也不依賴(lài)容器,aop等任何不該依賴(lài)的東西,就是一個(gè)傳統的嚴謹的ioc設計。
下面探討怎么從Yan
首先,因為這些注射都是基于某個(gè)不是容器創(chuàng )建的對象進(jìn)行的,我們用Binder
Binder injection = new Binder(){public Creator bind(Object obj){return Components.value(obj).bean(new String[]{"smtpService"});}};
這個(gè)bind()函數返回的Component對象就負責對這個(gè)參數??obj??的smtpService進(jìn)行注射。
接下來(lái),需要把這個(gè)Binder對象轉換成一個(gè)生成Injector實(shí)例的Component,InjectorHelper
void registerBankAccountService(Container yan){Component some_dao_component = ...;Binder injection = ...;InjectorHelper helper = new InjectorHelper();Component injector = helper.getInjectorComponent(Injector.class, injection);yan.registerComponent("bankaccount_service",Components.ctor(BankAccountService.class).withArgument(0, some_dao_component).withArgument(1, injector);}聯(lián)系客服