source: http://www.matrix.org.cn/resource/article/2007-01-17/Agile+Tips_2d4b7190-a5ee-11db-8440-755941c7293d.html
摘要:
要判斷一個(gè)代碼是不是包含了“不合適的依賴”,共有四個(gè)方法:1.看代碼:有沒有互相依賴?2.認(rèn)真想想:它真正需要的是什么?3.推測(cè)一下:它在以后的系統(tǒng)中可以重用嗎?4.到要重用的時(shí)候就知道了:現(xiàn)在我要重用這個(gè)類,能不能重用? 如果現(xiàn)在有一個(gè)類Parent,里面有個(gè)屬性的類型是Child,add的方法里面還有個(gè)參數(shù)的類型是Girl:
class Parent{
????????Child child;
????????void add(Girl girl){
?????????? ...
????????}
???? }
???? 因?yàn)樯厦鍼arent里面用到了Child跟Girl這兩個(gè)類,我們就說,Parent引用了類Child跟類Girl。現(xiàn)在的問題是,如果Child這個(gè)類或者Girl這個(gè)類編譯不過的話,那么Parent這個(gè)類也編譯不了了。也就是說,Parent依賴于Child跟Girl。這章講述的,就是因?yàn)橐恍╊惖囊蕾囋斐傻臒o(wú)法重用的問題。
示例 這是一個(gè)處理ZIP的程序。用戶可以在主窗口中先輸入要生成的目標(biāo)zip的路徑,比如c:\f.zip ,然后輸入他想壓縮到這個(gè)zip的源文件的路徑,比如
c:\f2.doc和c:\f2.doc 。然后這個(gè)程序就會(huì)開始?jí)嚎sf1.doc和f2.doc,生成f.zip文件。在壓縮各個(gè)源文件的時(shí)候,主窗口下的狀態(tài)欄都要顯示相關(guān)的信息。比如,在壓縮c:\f2.doc的時(shí)候,狀態(tài)欄就顯示"正在壓縮 c:\f2.zip"。??????????????????????????????????????????????????????????????????????????????
目前的代碼就是????????????????????????????????????????????????????????????????????????????
????class ZipMainFrame extends Frame {????????????????????????????????????????????????????????????
?????? StatusBar sb;??????????????????????????????????????????????????????????????????????????????
?????? void makeZip() {????????????????????????????????????????????????????????????????????????????
?????????? String zipFilePath;????????????????????????????????????????????????????????????????????
?????????? String srcFilePaths[];??????????????????????????????????????????????????????????????????
?????????? //根據(jù)UI上給zipFilePath和srcFilePaths賦值??????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? ZipEngine ze = new ZipEngine();????????????????????????????????????????????????????????
?????????? ze.makeZip(zipFilePath, srcFilePaths, this);????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
?????? void setStatusBarText(String statusText) {??????????????????????????????????????????????????
?????????? sb.setText(statusText);????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}??
????
????class ZipEngine {??????????????????????????????????????????????????????????????????????????????
?????? void makeZip(String zipFilePath, String srcFilePaths[], ZipMainFrame f) {??????????????????
?????????? //在該路徑上創(chuàng)建zip文件????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? for (int i = 0; i < srcFilePaths.length; i++) {????????????????????????????????????????
?????????????? //將srcFilePaths[i]的文件加到壓縮包中??????????????????????????????????
?????????????? ...????????????????????????????????????????????????????????????????????????????????
?????????????? f.setStatusBarText("Zipping "+srcFilePaths[i]);????????????????????????????????????
?????????? }??????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}
??????????????????????????????????????????????????????????????????????????????????????????????
我們還有一個(gè)存貨管理系統(tǒng),里面有一些程序的數(shù)據(jù)文件,經(jīng)常需要壓縮起來(lái)備份。這些源數(shù)據(jù)文件都有固定的路徑,所以就不需要用戶特地去輸入路徑了。現(xiàn)在我們想直接把上面的這個(gè)ZipEngine類拿過來(lái)重用。這個(gè)存貨管理系統(tǒng)也有一個(gè)主窗口,同樣在壓縮待備份文件時(shí),狀態(tài)欄上面也要顯示目前正在壓縮的文件名稱。
現(xiàn)在,問題來(lái)了。我們希望可以在不用修改代碼的情況下直接重用ZipEngine這個(gè)類。但看了上面的代碼以后我們發(fā)現(xiàn):在調(diào)用makeZip這個(gè)方法時(shí),還需要一個(gè)傳遞一個(gè)ZipMainFrame類型的參數(shù)進(jìn)來(lái)。可是很明顯我們現(xiàn)在的這個(gè)存貨管理系統(tǒng)里面并沒有ZipMainFrame這樣的類。也就是說,現(xiàn)在ZipEngine這個(gè)類,在我們的這個(gè)存貨管理系統(tǒng)中用不了了。
再往遠(yuǎn)一點(diǎn)想,好像其他的系統(tǒng),一般也不會(huì)有ZipMainFrame這個(gè)類。即使類名一樣的,里面所做的功能也不一樣。那其他的系統(tǒng)也重用不了這個(gè)ZipEngine類了。
“不合適的依賴”,讓代碼很難被重用 因?yàn)閆ipEngine引用了ZipMainFrame這個(gè)類,當(dāng)我們想重用ZipEngine的時(shí)候,我們就需要將ZipMainFrame也加進(jìn)來(lái),調(diào)用ZipEngine的makeZip方法時(shí),還要構(gòu)造一個(gè)ZipMainFrame對(duì)象傳給它。而在新的環(huán)境中,我們不可能有一個(gè)同樣的ZipMainFrame,也不可能特地為了調(diào)用這個(gè)方法,隨便創(chuàng)建一個(gè)ZipMainFrame對(duì)象給它。
一般來(lái)說,如果一個(gè)類A引用了一個(gè)類B,當(dāng)我們想要重用A這個(gè)類時(shí),我們就還得將B這個(gè)類也加進(jìn)我們的系統(tǒng)。如果B引用了C,那么B又將C也一起拉了進(jìn)來(lái)。而如果B或者C在一個(gè)新的系統(tǒng)中沒有意義,或者壓根兒不應(yīng)該存在的情況下,真正我們想要用的A這個(gè)類也用不了了。
因此,“不合適的依賴”讓代碼很難被重用。
為了可以重用ZipEngine,首先,我們得讓ZipEngine不再引用ZipMainFrame。或者說,讓ZipEngine不用依賴于ZipMainFrame。
那怎么做呢?回答這個(gè)問題之前,我們先回答另一個(gè)問題:給你一段代碼,你怎么判斷這段代碼是不是包含了“不合適的依賴”?“不合適”這個(gè)詞定義的標(biāo)準(zhǔn)又是什么?
怎么判斷是“不合適的依賴” 方法1:
一個(gè)簡(jiǎn)單的方法就是:我們先看一下這段代碼里面有沒有一些互相循環(huán)的引用。比如,ZipMainFrame引用了ZipEngine這個(gè)類,而ZipEngine又引用了ZipMainFrame。我們管這樣的類叫“互相依賴”。互相依賴也是一種代碼異味,我們就認(rèn)定這樣的代碼,是“不合適的依賴”。
這個(gè)方法很簡(jiǎn)單。不過,這種方法并不能包含全部情況,并不是所有有“不合適的依賴”的代碼,都是這種互相依賴。
方法2:
另一個(gè)方法比較主觀:在檢查代碼的時(shí)候,我們問自己:對(duì)于它已經(jīng)引用的這些類,是它真正需要引用的嗎?對(duì)于ZipEngine,它真的需要ZipMainFrame這個(gè)類嗎?ZipEngine只是改變ZipMainFrame的狀態(tài)欄上的信息。是不是只有引用了ZipMainFrame才能滿足這樣的需求,其他類行不行?有沒有一個(gè)類可以取代ZipMainFrame呢?而實(shí)際上,ZipEngine并不是一定要引用ZipMainFrame的。它想引用的,其實(shí)只是一個(gè)可以顯示信息的狀態(tài)欄而已。
因此,我們就將代碼改為:
????class ZipEngine {??????????????????????????????????????????????????????????????????????????????
?????? void makeZip(String zipFilePath, String srcFilePaths[], StatusBar statusBar) {??????????????
?????????? //在該路徑上創(chuàng)建zip文件????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? for (int i = 0; i < srcFilePaths.length; i++) {????????????????????????????????????????
?????????????? //將srcFilePaths[i]的文件加到壓縮包中??????????????????????????????????
?????????????? ...????????????????????????????????????????????????????????????????????????????????
?????????????? statusbar.setText("Zipping "+srcFilePaths[i]);??????????????????????????????????????
?????????? }??????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}????????
??????????????????????????????????????????????????????????????????????????????????????
現(xiàn)在,ZipEngine只是引用了StatusBar,而不再是ZipMainFrame了。可是這樣好嗎?相對(duì)好一些!因?yàn)镾tatusBar比較通用(至少有StatusBar這個(gè)類的系統(tǒng)比ZipMainFrame多多了),這樣的話,ZipEngine這個(gè)類的可重用性就大幅改觀了。
不過,這樣的方法還是太主觀了。沒有一個(gè)既定的標(biāo)準(zhǔn),可以判斷ZipEngine到底需要的是什么樣的東西。比如,我們就說,ZipEngine其實(shí)想要的也不是一個(gè)狀態(tài)欄,它只是想調(diào)用一個(gè)可以顯示一些信息的接口而已(而不是一個(gè)狀態(tài)欄這么大的一個(gè)對(duì)象)。????????????????????????????????????????????????????????????????????
方法3:
第3種方法也很主觀:在設(shè)計(jì)類的時(shí)候,我們先預(yù)測(cè)一個(gè)以后可能會(huì)重用這個(gè)類的系統(tǒng)。然后再判斷,在那樣的系統(tǒng)中,這個(gè)類能不能被重用?如果你自己都覺得以后的系統(tǒng)不能重用這個(gè)類的話,你就斷定,這個(gè)類包含“不合適的依賴”了。
比如,我們?cè)谠O(shè)計(jì)完ZipEngine這個(gè)類時(shí),我們就想一下,這個(gè)類能在別的系統(tǒng)重用嗎?可是好像別的系統(tǒng),不會(huì)有ZipMainFrame這個(gè)類,至少一個(gè)沒有GUI的系統(tǒng)會(huì)有這樣的類!這樣的話,那它就不應(yīng)該引用ZipMainFrame這個(gè)類了。這個(gè)方法其實(shí)也很主觀,不怎么實(shí)用。每個(gè)人預(yù)測(cè)的可能性都不一樣。
方法4
第4個(gè)方法比較簡(jiǎn)單而且客觀了。當(dāng)我們想在一個(gè)新系統(tǒng)中重用這個(gè)類,卻發(fā)現(xiàn)重用不了時(shí),我們就判斷,這個(gè)類包含了“不合適的依賴”。比如,我們?cè)诖尕浌芾硐到y(tǒng)中,要重用ZipEngine的時(shí)候,我們才發(fā)現(xiàn),這個(gè)類重用不了。這時(shí)我們就認(rèn)定,這個(gè)類有“不合適的依賴”。
后一種方法是個(gè)“懶惰而被動(dòng)的”方法,因?yàn)槲覀冋嬲朐诰唧w的項(xiàng)目中重用的時(shí)候,才能判斷出來(lái)。不過這也是個(gè)很有效的方法。
總結(jié) 要判斷一個(gè)代碼是不是包含了“不合適的依賴”,共有四個(gè)方法:
?? 1.看代碼:有沒有互相依賴?
?? 2.認(rèn)真想想:它真正需要的是什么?
?? 3.推測(cè)一下:它在以后的系統(tǒng)中可以重用嗎?
?? 4.到要重用的時(shí)候就知道了:現(xiàn)在我要重用這個(gè)類,能不能重用? 方法1跟4是最簡(jiǎn)單的方法,推薦初學(xué)者可以這樣來(lái)判斷。有更多的設(shè)計(jì)經(jīng)驗(yàn)了,再用方法2跟3會(huì)好一些。
怎么讓ZipEngine不再引用(依賴于)ZipMainFrame
現(xiàn)在我們來(lái)看看,怎么讓ZipEngine不再引用ZipMainFrame。其實(shí),在介紹方法2的時(shí)候,我們就已經(jīng)通過思考發(fā)現(xiàn),ZipEngine這個(gè)類真正需要的是什么,也找出了解決辦法。不過因?yàn)榉椒?相對(duì)來(lái)講并不是那么簡(jiǎn)單就可以用好的,所以我們先假裝不知道方法2的結(jié)果。
我們用方法4。現(xiàn)在我們是在做一個(gè)文字模式的系統(tǒng)(沒有狀態(tài)欄了,我們只能直接在沒有圖形的屏幕上顯示這些信息),發(fā)現(xiàn)ZipEngine不能重用了。怎么辦?
因?yàn)槲覀儾荒苤赜肸ipEngine,我們只好先將它的代碼復(fù)制粘貼出來(lái),然后再修改成下面的代碼:
????class TextModeApp {
?????? void makeZip() {
?????????? String zipFilePath;
?????????? String srcFilePaths[];
?????????? ...??
?????????? ZipEngine ze = new ZipEngine();
?????????? ze.makeZip(zipFilePath, srcFilePaths);
?????? }????????
????}??????????
????class ZipEngine {
?????? void makeZip(String zipFilePath, String srcFilePaths[]) {
?????????? //在該路徑上創(chuàng)建zip文件
?????????? ...??
?????????? for (int i = 0; i < srcFilePaths.length; i++) {
?????????????? //將srcFilePaths[i]的文件加到壓縮包中
?????????????? ...
?????????????? System.out.println("Zipping "+srcFilePaths[i]);????????????????????????????????????
?????????? }??????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}??
??????????????????????????????????????????????????????????????????????????????????????????
再看一下原來(lái)的代碼是:
????class ZipEngine {??????????????????????????????????????????????????????????????????????????????
?????? void makeZip(String zipFilePath, String srcFilePaths[], ZipMainFrame f) {??????????????????
?????????? //在該路徑上創(chuàng)建zip文件????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? for (int i = 0; i < srcFilePaths.length; i++) {????????????????????????????????????????
?????????????? //將srcFilePaths[i]的文件加到壓縮包中??????????????????????????????????
?????????????? ...????????????????????????????????????????????????????????????????????????????????
?????????????? f.setStatusBarText("Zipping "+srcFilePaths[i]);????????????????????????????????????
?????????? }??????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}??????????????????????????????????????????????????????????????????????????????????????????????
很明顯,這里面有很多的重復(fù)代碼(代碼異味)。要消除這樣的代碼異味,我們就先用偽碼讓這兩段代碼看起來(lái)一樣。比如,改成:
????class ZipEngine {??????????????????????????????????????????????????????????????????????????????
?????? void makeZip(String zipFilePath, String srcFilePaths[]) {??????????????????????????????????
?????????? //在該路徑上創(chuàng)建zip文件????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? for (int i = 0; i < srcFilePaths.length; i++) {????????????????????????????????????????
?????????????? //將srcFilePaths[i]的文件加到壓縮包中??????????????????????????????????
?????????????? ...????????????????????????????????????????????????????????????????????????????????
?????????????? 顯示信息。。。??????????????????????????????????????????????????????????????????
?????????? }??????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}????????
??????????????????????????????????????????????????????????????????????????????????????
因?yàn)椤帮@示信息”具體出來(lái),有兩種實(shí)現(xiàn),所以我們現(xiàn)在創(chuàng)建一個(gè)接口,里面有一個(gè)方法用來(lái)顯示信息。這個(gè)方法可以直接取名為“showMessage”,而根據(jù)這個(gè)接口做的事,我們也可以直接將接口名取為“MessageDisplay”或者“MessageSink”之類的:
????interface MessageDisplay {????????????????????????????????????????????????????????????????????
?????? void showMessage(String msg);??????????????????????????????????????????????????????????????
????}??????????????????????????????????????????????????????????????????????????????????????????????
將ZipEngine改為:
????class ZipEngine {??????????????????????????????????????????????????????????????????????????????
?????? void makeZip(String zipFilePath, String srcFilePaths[], MessageDisplay??????????????????????
????msgDisplay) {??????????????????????????????????????????????????????????????????????????????????
?????????? //在該路徑上創(chuàng)建zip文件????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? for (int i = 0; i < srcFilePaths.length; i++) {????????????????????????????????????????
?????????????? //將srcFilePaths[i]的文件加到壓縮包中??????????????????????????????????
?????????????? ...????????????????????????????????????????????????????????????????????????????????
?????????????? msgDisplay.showMessage("Zipping "+srcFilePaths[i]);
?????????? }????
?????? }????????
????}??
????????
而MessageDisplay這個(gè)接口的兩個(gè)實(shí)現(xiàn)類就是:
????class ZipMainFrameMessageDisplay implements MessageDisplay {
?????? ZipMainFrame f;
?????? ZipMainFrameMessageDisplay(ZipMainFrame f) {
?????????? this.f = f;
?????? }????????
?????? void showMessage(String msg) {
?????????? f.setStatusBarText(msg);
?????? }????????
????}??????????
????class SystemOutMessageDisplay implements MessageDisplay {
?????? void showMessage(String msg) {
?????????? System.out.println(msg);
?????? }????????
????}
??????????
現(xiàn)在兩個(gè)系統(tǒng)也相應(yīng)的做了修改:
????class ZipMainFrame extends Frame {
?????? StatusBar sb;
?????? void makeZip() {
?????????? String zipFilePath;
?????????? String srcFilePaths[];
?????????? //根據(jù)UI上給zipFilePath和srcFilePaths賦值
?????????? ...??
?????????? ZipEngine ze = new ZipEngine();
?????????? ze.makeZip(zipFilePath, srcFilePaths, new ZipMainFrameMessageDisplay(this));
?????? }????????
?????? void setStatusBarText(String statusText) {
?????????? sb.setText(statusText);
?????? }????????
????}????????
????
????class TextModeApp {
?????? void makeZip() {
?????????? String zipFilePath;
?????????? String srcFilePaths[];
?????????? ...??
?????????? ZipEngine ze = new ZipEngine();
?????????? ze.makeZip(zipFilePath, srcFilePaths, new SystemOutMessageDisplay());
?????? }????????
????}
??????????
改進(jìn)后的代碼
下面就是改進(jìn)完的代碼。為了讓代碼看起來(lái)清楚一些,我們用了Java的內(nèi)類:
????interface MessageDisplay {
?????? void showMessage(String msg);??????????????????????????????????????????????????????????????
????}??????????????????????????????????????????????????????????????????????????????????????????????
????class ZipEngine {??????????????????????????????????????????????????????????????????????????????
?????? void makeZip(String zipFilePath, String srcFilePaths[], MessageDisplay??????????????????????
????msgDisplay) {??????????????????????????????????????????????????????????????????????????????????
?????????? //在該路徑上創(chuàng)建zip文件????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? for (int i = 0; i < srcFilePaths.length; i++) {????????????????????????????????????????
?????????????? //將srcFilePaths[i]的文件加到壓縮包中??????????????????????????????????
?????????????? ...????????????????????????????????????????????????????????????????????????????????
?????????????? msgDisplay.showMessage("Zipping "+srcFilePaths[i]);????????????????????????????????
?????????? }??????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}????????????
????
????class ZipMainFrame extends Frame {????????????????????????????????????????????????????????????
?????? StatusBar sb;??????????????????????????????????????????????????????????????????????????????
?????? void makeZip() {????????????????????????????????????????????????????????????????????????????
?????????? String zipFilePath;????????????????????????????????????????????????????????????????????
?????????? String srcFilePaths[];??????????????????????????????????????????????????????????????????
?????????? //根據(jù)UI上給zipFilePath和srcFilePaths賦值??????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? ZipEngine ze = new ZipEngine();????????????????????????????????????????????????????????
?????????? ze.makeZip(zipFilePath, srcFilePaths, new MessageDisplay() {????????????????????????????
?????????????? void showMessage(String msg) {??????????????????????????????????????????????????????
??????????????????setStatusBarText(msg);??????????????????????????????????????????????????????????
?????????????? }??????????????????????????????????????????????????????????????????????????????????
?????????? });????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
?????? void setStatusBarText(String statusText) {??????????????????????????????????????????????????
?????????? sb.setText(statusText);????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}????????????
????
????class TextModeApp {????????????????????????????????????????????????????????????????????????????
?????? void makeZip() {????????????????????????????????????????????????????????????????????????????
?????????? String zipFilePath;????????????????????????????????????????????????????????????????????
?????????? String srcFilePaths[];??????????????????????????????????????????????????????????????????
?????????? ...????????????????????????????????????????????????????????????????????????????????????
?????????? ZipEngine ze = new ZipEngine();????????????????????????????????????????????????????????
?????????? ze.makeZip(zipFilePath, srcFilePaths, new MessageDisplay() {????????????????????????????
?????????????? void showMessage(String msg) {??????????????????????????????????????????????????????
??????????????????System.out.println(msg);????????????????????????????????????????????????????????
?????????????? }??????????????????????????????????????????????????????????????????????????????????
?????????? });????????????????????????????????????????????????????????????????????????????????????
?????? }??????????????????????????????????????????????????????????????????????????????????????????
????}
??????????????????????????????????????????????????????????????????????????????????????????????
引述????????????????????????????????????????????????????????????????????????????????????????
依賴反轉(zhuǎn)原則(Dependency Inversion Principle )表述:抽象不應(yīng)該依賴于具體,高層的比較抽象的類不應(yīng)該依賴于低層的比較具體的類。當(dāng)這種問題出現(xiàn)的時(shí)候,我們應(yīng)該抽取出更抽象的一個(gè)概念,然后讓這兩個(gè)類依賴于這個(gè)抽取出來(lái)的概念。更多的信息,可以看:
http://www.objectmentor.com/resources/articles/dip.pdf??????????????????????????????????????
http://c2.com/cgi/wiki?DependencyInversionPrinciple??????????