欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
解依賴(lài)與接縫
解依賴(lài)與接縫
來(lái)源: 捷道   發(fā)布時(shí)間: 2011-11-24 13:05  閱讀: 1197 次  推薦: 0   原文鏈接    [收藏]   

接縫(seam)是Michael C. Feathers提出的概念。Feathers在Working Effectively with Legacy Code一書(shū)中對接縫的定義如下:

接縫,顧名思義,就是指程序中的一些特殊的點(diǎn),在這些點(diǎn)上你無(wú)需作任何修改就可以達到改動(dòng)程序行為的目的。

“接縫”這個(gè)詞語(yǔ)不太好理解,根據我的理解,大約還是依賴(lài)點(diǎn)的含義。通過(guò)事先找到依賴(lài)點(diǎn),并采取一定方式解除依賴(lài),就能夠改善代碼質(zhì)量,尤其是針對遺留代碼而言。準確而言,我們尋找接縫以及解依賴(lài),就是為了代碼能夠具有好的可重用性與可擴展性,尤其是當我們能解除對其他外部服務(wù)的依賴(lài)時(shí),可以帶來(lái)程序的可測試性。

最近項目組的同事和我討論了這樣一個(gè)滿(mǎn)足可測試性的問(wèn)題。項目中需要對返回的響應信息PlatformResponse進(jìn)行處理,這些信息會(huì )根據不同的StatusCode,得到不同的提示或出錯信息。為了避免分支語(yǔ)句的判斷,同事利用hash table將StatusCode與提示(出錯)信息進(jìn)行了映射,然后根據當前的StatusCode就可以返回對應的結果。返回結果后,還需要調用外部服務(wù)對消息進(jìn)行處理,例如消息的輸出。由于之前相關(guān)的類(lèi)PlatformResponse并沒(méi)有提供這一邏輯,相關(guān)服務(wù)要返回消息時(shí),直接返回了PlatformResponse對象,然后再由客戶(hù)端根據當前的StatusCode來(lái)判斷,輸出相關(guān)的提示信息,所以同事將這些邏輯寫(xiě)到了擴展方法中,例如定義PlatformResponseHelper靜態(tài)類(lèi):

public static class PlatformResponseHelper {
private static HashTable<String,String> messageMapping //此處略    public static void Output(this PlatformResponse response) {
        ServiceLocator.Lookup
<IMessageWriter>.Write(messageMapping[response.StatusCode]);
    }
}

通過(guò)引入擴展方法,Controller得到的PlatformResponse對象就可以通過(guò)調用擴展方法Output()輸出獲得的提示(出錯)信息。注意,在上面的代碼中,ServiceLocator是一個(gè)單例的服務(wù)定位器對象,通過(guò)它可以獲得注冊的服務(wù)。在Controller中,同樣調用了ServiceLocator來(lái)獲得它所需的業(yè)務(wù)服務(wù)。

現在,我們需要進(jìn)行單元測試。項目之前已經(jīng)為ServiceLocator提供了Mock對象,并且該對象在Controller中也是通過(guò)依賴(lài)注入的方式獲得的。所以,在測試Controller時(shí),可以通過(guò)注入模擬的ServiceLocator對象進(jìn)行測試,從而解除與外部服務(wù)之間的依賴(lài)關(guān)系?,F在,在增加了PlatformResponse的擴展方法時(shí),遇到了難題,即如何解除擴展方法與ServiceLocator之間的依賴(lài)關(guān)系?

顯然,這里的ServiceLocator.Lookup<IMessageWriter>.Write()方法調用就是前面所說(shuō)的“接縫”。我們希望在單元測試中不依賴(lài)于ServiceLocator,這就需要解除PlatformResponse與ServiceLocator之間的耦合關(guān)系。同事希望既能達到可測試性的目的,又要保障調用的簡(jiǎn)單。

在面向對象設計中,最常見(jiàn)的解除依賴(lài)的方法是職責分離以及抽象,或者利用反射或IOC容器來(lái)解除具體依賴(lài)。由于要解除與ServiceLocator的耦合關(guān)系,再加上調用PlatformResponse相關(guān)方法的Controller也是通過(guò)依賴(lài)注入ServiceLocator對象的,所以我首先想到將ServiceLocator轉移到擴展方法的外部,通過(guò)傳入參數的方式注入依賴(lài)。由于這個(gè)對象是單例的,因此Controller獲得的ServiceLocator也就是PlatformResponse需要的對象。當我們調用PlatformResponse的Output()方法時(shí),可以將Controller獲得的ServiceLocator對象作為方法參數傳給PlatformResponse。在Controller層,我們利用依賴(lài)注入注入Mock對象,就可以達到較好的可測試性了。

然而,倘若要這樣做,就需要將調用代碼改為:

businessService.Response.Output(serviceLocator);

同事覺(jué)得在調用Output()方法時(shí),還需要傳入ServiceLocator對象,實(shí)在不夠優(yōu)雅而簡(jiǎn)潔。

由于C#的擴展方法有很多限制,例如它要求必須是靜態(tài)類(lèi)和靜態(tài)方法,很難利用OO的一些特性,所以我想到的第二個(gè)方案是不采用擴展方法,而是將之前的邏輯直接封裝到PlatformResponse中。我們可以將Output()方法定義為虛方法,然后再為測試定義PlatformResponse的子類(lèi),它將作為測試使用的Mock類(lèi),重寫(xiě)Output()方法。遺憾的是,系統基本上都是在調用外部服務(wù)的時(shí)候才獲得的PlatformResponse對象。我們不可能去修改服務(wù)對象,使其在單元測試時(shí)返回該類(lèi)的子類(lèi)對象。

第三條路是轉移職責,將擴展方法Output()轉移到一個(gè)專(zhuān)門(mén)的類(lèi),例如OutputMessage,由它來(lái)負責管理StatusCode與提示(出錯)消息之間的映射關(guān)系,以及消息的輸出,然后由子類(lèi)重寫(xiě)消息處理的邏輯,完成模擬。例如代碼:

public class OutputMessage {
private PlatformResponse response;
private HashTable<String, String> messageMapping = //此處略    public OutputMessage(PlatformResponse response) {
this.response = response;
    }
public void Output() {
        OutputInternal();
    }
protected virtual void OutputInternal() {
        ServiceLocator.Lookup
<IMessageWriter>.Write(messageMapping[response.StatusCode]);
    }
}
public class MockOutputMessage {
public MockOutputMessage(PlatformResponse response):base(response) {}
protected override void OutputInternal() {
//模擬國際化服務(wù)對消息進(jìn)行處理;   }
}

實(shí)際上這一方案是第二種方案的一種變化。因為我們無(wú)法修改一個(gè)已經(jīng)被廣泛使用的類(lèi),所以只能在引入新職責的時(shí)候,通過(guò)引入新生類(lèi)來(lái)完成職責的增加,并利用子類(lèi)重寫(xiě)的方式達到可測試的目的。

可是這一方案實(shí)際上更無(wú)法達到同事的目標,因為改動(dòng)后的調用變得比第一種方案更復雜:

   new OutputMessage(businessService.Response).Output();

我們必須考慮OutputMessage對象的創(chuàng )建,同事還需要將PlatformResponse對象傳入,再調用它的Output()方法。雖然不需要傳入方法參數,但對象的創(chuàng )建以及構造函數參數的傳入,反而讓事情變得更復雜。

那么,應該怎么辦?同事的理想目標是調用簡(jiǎn)單。就目前而言,在C#中,只有擴展方法才能讓我們對PlatformResponse對象的message處理顯得如此的自然而簡(jiǎn)潔。再加上PlatformMessage對象已經(jīng)被廣泛使用,因此從PlatformMessage類(lèi)的角度進(jìn)行處理,就變得不再可能。

讓我們再來(lái)仔細思考“接縫”的問(wèn)題。是誰(shuí)引入了依賴(lài)點(diǎn)?接縫是調用ServiceLocator這條語(yǔ)句,而它的目的實(shí)際上是需要獲得IMessageWriter。是這個(gè)外部服務(wù)成為測試的障礙。所以解決的重點(diǎn)應該是解除與IMessageWriter之間的依賴(lài)。要這樣做,就需要修改Output()擴展方法,使其能夠傳入IMessageWriter對象。這種改進(jìn)事實(shí)上與第一種方案沒(méi)有什么區別,唯一的不同是它依賴(lài)于更小的接口,而不是全局的ServiceLocator對象。我認為,這已經(jīng)是一個(gè)最好的方案了。但是同事依舊執著(zhù)于調用的簡(jiǎn)單性。他認為,不能為了單元測試,而改變客戶(hù)端調用的方式。

現在,我們已經(jīng)明白擴展方法是最簡(jiǎn)單的實(shí)現方式,糾結僅僅在于IMessageWriter服務(wù)的獲取方式而已。在產(chǎn)品代碼中,我們可以通過(guò)ServiceLocator來(lái)獲得IMessageWriter對象,而在測試的時(shí)候,我們又需要模擬該服務(wù)對象。若要兩全齊美,只有區分測試與生產(chǎn)環(huán)境。事實(shí)上,這是我最初想到的做法,就是引入預定義來(lái)區分測試與真正的生產(chǎn)環(huán)境。但同事無(wú)法接受這種C++所主要采取的預定義做法。因此,我唯一能想到的是修改PlatformMessage類(lèi)的定義,提供設置IMessageWriter的屬性(因為C#并不支持擴展屬性),并在Output()方法中判斷IMessageWriter對象是否為null。如果為null,則說(shuō)明它沒(méi)有在測試環(huán)境下注入,這就需要通過(guò)ServiceLocator獲得。

public class PlatformResponse {
private IMessageWriter writer = null;
public IMessageWriter MessageWriter {
getset;
    }
}
public static class PlatformResponseHelper {
private static HashTable<String,String> messageMapping //略去
public static void Output(this PlatformResponse response) {
        String message 
= messageMapping[response.StatusCode];
//如果不為null,說(shuō)明是測試注入了該對象;       if (response.MessageWriter !=null) {
           response.MessageWriter.Write(message);
       } 
else {
           ServiceLocator.Lookup
<IMessageWriter>.Write(message);
       }
    }
}

雖然我修改了PlatformResponse類(lèi)的定義,但由于需要調用或創(chuàng )建PlatformResponse對象的外部服務(wù)并不需要新增加的MessageWriter屬性,因此這樣的修改實(shí)際上是擴展,并不會(huì )影響到以前的代碼。這就是我唯一能夠想到的滿(mǎn)足同事要求的方案。在測試時(shí),我們可以通過(guò)為PlatformResponse注入模擬的IMessageWriter對象,而在真正的產(chǎn)品代碼中,則無(wú)需為它設置MessageWriter屬性,而是直接調用它的擴展方法Output()。美中不足之處在于它為調用者提供了一定的開(kāi)放性,使得調用者能夠自由設置MessageWriter屬性,破壞了對象的封裝。為使這種破壞帶來(lái)的影響降到最低,在單元測試放在同一個(gè)項目的前提下,可以考慮將MessageWriter屬性定義為internal。

我們常常需要在靈活性和簡(jiǎn)單性之間進(jìn)行設計權衡。大多數情況下,都可能產(chǎn)生非此即彼的選擇。要做到兩全齊美真的很難。從面向對象的角度來(lái)看,我不認為最后的方案是最佳方案。其實(shí),通過(guò)方法參數注入IMessageWriter的做法已經(jīng)足夠好了,它并沒(méi)有加大結構與調用的復雜性。無(wú)論怎樣,設計總是見(jiàn)仁見(jiàn)智的問(wèn)題,就看大家的選擇了。唯一需要遵循的原則,就是設計必須結合具體的場(chǎng)景來(lái)做出正確的決定。

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
第二節 RESPONSE對象(上)
小白學(xué) Python 爬蟲(chóng)(37):爬蟲(chóng)框架 Scrapy 入門(mén)基礎(五) Spider Middleware
Postman全局變量及調用
自定義AuthorizeAttribute
糾結了半天的 java.lang.IllegalStateException: getOutputStream() has already been called for this response
httpclient 多線(xiàn)程高并發(fā)Get請求
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久