開發,從需求出發 · 之三 春天在哪裏


《西遊降魔》裏面的《兒歌三百首》裏面有首兒歌叫做《春天在哪裏》

歌詞是這樣的:

春天在哪裏

春天在哪裏

春天就在小朋友的眼睛裏


通過俺的渣英語翻譯之後是這樣的:

where spring is

where spring is

the fucking spring is

in javatar's eyes

yo

yo

check it out


我相信,java程序員已經意識到我說的春天是什麼了:)

不過spring跟我們現在說的東東有關係麼?暫時還沒有-_-b

言歸正傳,開始我們上一章節所說的,把問題搞複雜點兒。


我們假設這個搜索業務需要lucene和mysql的支持。

通過lucene的檢索獲得文檔ID,然後根據ID去mysql數據庫查詢,獲得文檔的標題和文本內容。


讓我們從需求開始,首先操作 SearchServiceInRealBiz 類:

package cn.com.sitefromscrath.service;

import java.util.ArrayList;
import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public class SearchServiceInRealBiz implements SearchService {
	
	public List search(String keywords) {
		
		int[] idlist = findDocIDs(keywords);
		List<Result> results = getResultsByDocIDs(idlist);
		
		return results;
		
	}
}

當然,你會看到你的eclipse會有錯誤提示信息:


因爲這兩個方法沒有定義。但是沒有關係,eclipse本身自帶工具,

自動生成的代碼是這樣子的:


實現他們就好啦:)

我們按照先前的開發流程,給出模擬實現先:

package cn.com.sitefromscrath.service;

import java.util.ArrayList;
import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public class SearchServiceInRealBiz implements SearchService {
	
	public List search(String keywords) {
		
		int[] idlist = findDocIDs(keywords);
		List<Result> results = getResultsByDocIDs(idlist);
		
		return results;
		
	}

	private List<Result> getResultsByDocIDs(int[] idlist) {
		List<Result> results = new ArrayList<Result>(idlist.length);
		
		for(int i = 0; i < idlist.length; i++) {
			int id = idlist[i];
			String title = "result " + id;
			String content = "something..................";
			results.add(new Result(title, content));
		}
		
		return results;
	}

	private int[] findDocIDs(String keywords) {
		return new int[]{1, 2, 3, 4};
	}
}
請記住,每一步我們都應該去做測試,這裏就不一一贅述。

比如,我們可以把BeanFactory run一次,看看main方法的輸出會不會發生非預期的變化。

現在,雖然我沒有運行tomcat查看網頁,但是我能肯定,網頁所展示的內容一定是正確的。


由於java程序員的本性發作,我覺得寫一個DAO層,然後。。。當然是接口和實現分離蝦米的。。。

lucene的實現(模擬階段):

接口:

package cn.com.sitefromscrath.dao;

public interface LuceneDAO {
	
	public int[] findDocIDs(String keywords);
	
}

實現:

package cn.com.sitefromscrath.dao;

public class LuceneDAOMock implements LuceneDAO {

	@Override
	public int[] findDocIDs(String keywords) {		
		return new int[]{1, 2, 3, 4};
	}

}

Mysql的實現(模擬階段):

接口:

package cn.com.sitefromscrath.dao;

import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public interface MysqlDAO {
	
	public List<Result> getResultsByDocIDs(int[] idlist);
	
}

實現:

package cn.com.sitefromscrath.dao;

import java.util.ArrayList;
import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public class MysqlDAOMock implements MysqlDAO {

	@Override
	public List<Result> getResultsByDocIDs(int[] idlist) {
		List<Result> results = new ArrayList<Result>(idlist.length);
		
		for(int i = 0; i < idlist.length; i++) {
			int id = idlist[i];
			String title = "result " + id;
			String content = "something..................";
			results.add(new Result(title, content));
		}
		
		return results;
	}

}

然後,我們把 SearchServiceInRealBiz 的代碼從新組織一次,將:

package cn.com.sitefromscrath.service;

import java.util.ArrayList;
import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public class SearchServiceInRealBiz implements SearchService {
	
	public List search(String keywords) {
		
		int[] idlist = findDocIDs(keywords);
		List<Result> results = getResultsByDocIDs(idlist);
		
		return results;
		
	}

	private List<Result> getResultsByDocIDs(int[] idlist) {
	}

	private int[] findDocIDs(String keywords) {
	}
}

替換爲:

public class SearchServiceInRealBiz implements SearchService {
	
	public List search(String keywords) {
		
//		int[] idlist = findDocIDs(keywords);
//		List<Result> results = getResultsByDocIDs(idlist);
		
		LuceneDAO luceneDAO = new LuceneDAOMock();
		int[] idlist = luceneDAO.findDocIDs(keywords);
		
		MysqlDAO mysqlDAO = new MysqlDAOMock();
		List<Result> results = mysqlDAO.getResultsByDocIDs(idlist);
		
		return results;
		
	}
}

測試,stdout / eclipse console 輸出無誤。

[result 1]something..................
[result 2]something..................
[result 3]something..................
[result 4]something..................

當然,我們同樣會發現一個問題,我們現在的類是模擬的數據啊,以後怎麼切換呢?

還好,有工廠。既然我們在工廠裏切換了

SearchService
那麼,
LuceneDAO 
MysqlDAO
同樣可以在那裏處理,於是,同樣執行我上面提到的流程,

從需求開始,我需要一個什麼樣的方法,那就先定義什麼方法,然後利用eclipse工具生成方法的骨架 method skeleton,然後實現它。

首先改寫 SearchServiceInRealBiz :

然後,實現方法骨架:

package cn.com.sitefromscrath;

import java.util.List;

import javax.xml.rpc.ServiceFactory;

import cn.com.sitefromscrath.dao.LuceneDAO;
import cn.com.sitefromscrath.dao.LuceneDAOMock;
import cn.com.sitefromscrath.dao.MysqlDAO;
import cn.com.sitefromscrath.dao.MysqlDAOMock;
import cn.com.sitefromscrath.entity.Result;
import cn.com.sitefromscrath.service.SearchService;
import cn.com.sitefromscrath.service.SearchServiceMock;
import cn.com.sitefromscrath.service.SearchServiceInRealBiz;

public class BeanFactory {
	
	public static boolean MOCK = true; 
	
	public static Object getBean(String id) {
		if("searchService".equals(id)) {
			if(MOCK) {
				return new SearchServiceMock();
			} else {
				return getSearchService();
			}			
		}
		
		throw new RuntimeException("cannot find the bean with id :" + id);
	}
	
	public static LuceneDAO getLuceneDAO() {
		if(MOCK) {
			return new LuceneDAOMock();
		} else {
			throw new RuntimeException("cannot find the LuceneDAO bean");
		}		
	}
	
	public static MysqlDAO getMysqlDAO() {
		if(MOCK) {
			return new MysqlDAOMock();
		} else {
			throw new RuntimeException("cannot find the MysqlDAO bean");
		}		
	}
	
	public static SearchService getSearchService() {
		if(MOCK) {
			return new SearchServiceMock();
		} else {
			return new SearchServiceInRealBiz();
		}
	}
	
	public static void main(String ... arg) {
		String keywords = "test";
		SearchService searchService = (SearchService)BeanFactory.getBean("searchService");	
		List results = searchService.search(keywords);
		for(int i = 0; i < results.size(); i++) {
			Result result = (Result) results.get(i);
			System.out.print("[" + result.title + "]");
			System.out.println(result.content);
		}
	}

}

測試無誤。bingo!

I gotta get the GREEN BAR!!!(JUnit專用~~~)


現在,我開始發現

BeanFactory 
有點兒無處不在了,一旦需要修改,哪怕是改個 BeanFactory 的名字,如果沒有refactor工具,工作也是相當麻煩的。

這就是所謂的 “上帝類 God Class” “上帝對象 God Object”

爲了減少影響,至少,我應該儘可能的把 BeanFactory 的字樣從其他類裏面移出去。


讓我們高唱國際歌,“從來就沒有什麼救世主,也沒有神仙皇帝”,對BeanFactory進行大刀闊斧的革命~~!


在我們現在的代碼裏,僅有 SearchServiceInRealBiz 擁抱了 “上帝”。 因此,想想辦法:

public class SearchServiceInRealBiz implements SearchService {
	
	private LuceneDAO luceneDAO;
	private MysqlDAO mysqlDAO;	
	
	private SearchServiceInRealBiz(LuceneDAO luceneDAO, MysqlDAO mysqlDAO) {
		super();
		this.luceneDAO = luceneDAO;
		this.mysqlDAO = mysqlDAO;
	}

	public List search(String keywords) {
		
		int[] idlist = luceneDAO.findDocIDs(keywords);		
		List<Result> results = mysqlDAO.getResultsByDocIDs(idlist);
		
		return results;		
	}
}


當然 BeanFactory 肯定會報錯的,


必須說明我期待這樣的報錯(編譯時報錯),這樣我們才能發現對代碼進行調整之後會影響那些地方。然後隨之做出對應調整。

參考另外一種方式:

	private LuceneDAO luceneDAO;
	private MysqlDAO mysqlDAO;	
	
	public void setLuceneDAO(LuceneDAO luceneDAO) {
		this.luceneDAO = luceneDAO;
	}

	public void setMysqlDAO(MysqlDAO mysqlDAO) {
		this.mysqlDAO = mysqlDAO;
	}

	public List search(String keywords) {
		
		int[] idlist = luceneDAO.findDocIDs(keywords);		
		List<Result> results = mysqlDAO.getResultsByDocIDs(idlist);
		
		return results;		
	}

這樣eclipse不會報錯,但是。。。。。。。你失去了改正的機會!

混過了編譯時檢測,跑不掉運行時報錯D,親!而且,這個問題會隱蔽的讓你吐血~~~~

(其實,我在暗示你應該在spring的xml中選擇哪一種裝配方式……)


現在,讓我們修復BeanFactory 的報錯:

	public static SearchService getSearchService() {
		if(MOCK) {
			return new SearchServiceMock();
		} else {
			LuceneDAO luceneDAO = getLuceneDAO();
			MysqlDAO mysqlDAO = getMysqlDAO();
			
			return new SearchServiceInRealBiz(luceneDAO, mysqlDAO);
		}
	}

RUN一次,ok,沒有問題。 I LOVE GREE BAR!


《兒歌三百首》絕非浪得虛名~~


to be continued....

發佈了94 篇原創文章 · 獲贊 6 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章