從需求出發并不是一句空話,在開發過程中也是如此。
從需求出發,實質上是暗合了極限程式設計和測試驅動開發的一些思想。
鑒于網站開發是一個比較流行的方向,我打算從一個網站開始,闡述一下自己對“需求驅動開發“的了解,并将其引申到一個更廣泛的領域。
首先,我們假設一個需求:
我們需要實作一個類似google的網站,使用者通過web浏覽器通路,在首頁輸入框中查詢,傳回搜尋的結果。
效果如下圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc5WNXlFMGdEZzZ1RaZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zM1kDM0cTNwIzMycDM0EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
STEP 0,通過eclipse建立一個web項目:sitefromscratch,檔案結構如圖所示:
在WebRoot下新增一個jsp檔案:
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>search it!</h1>
<form action="" method="get">
<input type="text" value="site from scratch" name="keywords" />
<input type="submit" value="search!" />
</form>
<table bordercolor="grey" >
<tr>
<td>result 1</td><td>something..................</td>
</tr>
<tr>
<td>result 1</td><td>something..................</td>
</tr>
<tr>
<td>result 1</td><td>something..................</td>
</tr>
<tr>
<td>result 1</td><td>something..................</td>
</tr>
</table>
</body>
</html>
通過浏覽器通路 /sitefromscratch/search1.jsp 可以得到和目标一緻的效果。
STEP 2,通過界面所展示的内容,我們可以大緻估計出所需要的資料以及其格式,讓我們加入少量的代碼實作同樣的效果:
<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%!
class Result {
String title;
String content;
public Result(String title, String content) {
this.title = title;
this.content = content;
}
}
%>
<%
String keywords = "site from scratch";
List results = new ArrayList();
results.add(new Result("result 1", "something.................."));
results.add(new Result("result 2", "something.................."));
results.add(new Result("result 3", "something.................."));
results.add(new Result("result 4", "something.................."));
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>search it!</h1>
<form action="" method="get">
<input type="text" value="<%=keywords %>" name="keywords" />
<input type="submit" value="search!" />
</form>
<%
if(!results.isEmpty()) {
%>
<table bordercolor="grey" >
<%
for(int i = 0; i < results.size(); i++) {
Result result = (Result)results.get(i);
%>
<tr>
<td><%=result.title %></td><td><%=result.content %></td>
</tr>
<%
}
%>
</table>
<%
}
%>
</body>
</html>
這裡,我們構造了一批僞資料(同時建立了一個類),并通過對應的執行邏輯,得到了完全一緻的展示效果。
STEP 3,現在,該把送出、查詢、結果展示的流程走通了:
<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%!
class Result {
public String title;
public String content;
public Result(String title, String content) {
this.title = title;
this.content = content;
}
}
public List search(String keywords) {
List results = new ArrayList();
results.add(new Result("result 1", "something.................."));
results.add(new Result("result 2", "something.................."));
results.add(new Result("result 3", "something.................."));
results.add(new Result("result 4", "something.................."));
return results;
}
%>
<%
String keywords = request.getParameter("keywords");
if(keywords == null) keywords = "";
List results = search(keywords);
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>search it!</h1>
<form action="" method="get">
<input type="text" value="<%=keywords %>" name="keywords" />
<input type="submit" value="search!" />
</form>
<%
if(!results.isEmpty()) {
%>
<table bordercolor="grey" >
<%
for(int i = 0; i < results.size(); i++) {
Result result = (Result)results.get(i);
%>
<tr>
<td><%=result.title %></td><td><%=result.content %></td>
</tr>
<%
}
%>
</table>
<%
}
%>
</body>
</html>
這裡,我們構造了一個 List search(String keywords)方法,将業務邏輯和頁面展示分離開來,分别置于在jsp檔案中分離的區塊。
STEP 4,接着,為了保持頁面的簡潔,我們把定義的類和方法提取出來,用包組織起來:
package cn.com.sitefromscrath.entity;
public class Result {
public String title;
public String content;
public Result(String title, String content) {
this.title = title;
this.content = content;
}
}
package cn.com.sitefromscrath.service;
import java.util.ArrayList;
import java.util.List;
import cn.com.sitefromscrath.entity.Result;
public class SearchService {
public List search(String keywords) {
List results = new ArrayList();
results.add(new Result("result 1", "something.................."));
results.add(new Result("result 2", "something.................."));
results.add(new Result("result 3", "something.................."));
results.add(new Result("result 4", "something.................."));
return results;
}
}
不出意料,SearchService.java 和 Result.java的代碼就是直接從search.jsp中copy過去的。
值得一提的是,我們新增了一個BeanFactory類,作為工廠模式的一個實作,它簡單的通過指定的ID所對應的類傳回産生的執行個體。
如下所示:
package cn.com.sitefromscrath;
import cn.com.sitefromscrath.service.SearchService;
public class BeanFactory {
public static Object getBean(String id) {
if("searchService".equals(id)) {
return new SearchService();
}
throw new RuntimeException("cannot find the bean with id :" + id);
}
}
雖然它現在看來顯得畫蛇添足了一些,但是在我之後的展開論述中,它将占有很重要的位置。
現在,jsp檔案的内容看起來簡潔多了:
<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="cn.com.sitefromscrath.service.SearchService"%>
<%@page import="cn.com.sitefromscrath.BeanFactory"%>
<%@page import="cn.com.sitefromscrath.entity.Result"%>
<%
String keywords = request.getParameter("keywords");
if(keywords == null) keywords = "";
SearchService searchService = (SearchService)BeanFactory.getBean("searchService");
List results = searchService.search(keywords);
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>search it!</h1>
<form action="" method="get">
<input type="text" value="<%=keywords %>" name="keywords" />
<input type="submit" value="search!" />
</form>
<%
if(!results.isEmpty()) {
%>
<table bordercolor="grey" >
<%
for(int i = 0; i < results.size(); i++) {
Result result = (Result)results.get(i);
%>
<tr>
<td><%=result.title %></td><td><%=result.content %></td>
</tr>
<%
}
%>
</table>
<%
}
%>
</body>
</html>
STEP 5,聽說MVC是個很高科技的東西,我們也來實作一下:
新增一個servlet,作為 控制層 Controller
package cn.com.sitefromscrath.web;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.com.sitefromscrath.BeanFactory;
import cn.com.sitefromscrath.service.SearchService;
public class SearchServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String keywords = request.getParameter("keywords");
if(keywords == null) keywords = "";
SearchService searchService = (SearchService)BeanFactory.getBean("searchService");
List results = searchService.search(keywords);
request.setAttribute("keywords", keywords);
request.setAttribute("results", results);
request.getRequestDispatcher("/search5.jsp").forward(request, response);
}
}
新增一個jsp檔案,search5.jsp,作為視圖層 Viewer。
<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="cn.com.sitefromscrath.entity.Result"%>
<%
String keywords = (String)request.getAttribute("keywords");
List results = (List)request.getAttribute("results");
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>search it!</h1>
<form action="" method="get">
<input type="text" value="<%=keywords %>" name="keywords" />
<input type="submit" value="search!" />
</form>
<%
if(!results.isEmpty()) {
%>
<table bordercolor="grey" >
<%
for(int i = 0; i < results.size(); i++) {
Result result = (Result)results.get(i);
%>
<tr>
<td><%=result.title %></td><td><%=result.content %></td>
</tr>
<%
}
%>
</table>
<%
}
%>
</body>
</html>
web.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>SearchServlet</servlet-name>
<servlet-class>cn.com.sitefromscrath.web.SearchServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SearchServlet</servlet-name>
<url-pattern>/search</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
現在,讓我們再來run一次,http://localhost:8080/sitefromscratch/search
binggo,效果跟目标無差!
——————————————————————————————————————————————————————————
嗯,到這裡,我們到底達到了什麼目的?資料還是假的啊,有這個必要嗎?有這個必要嗎?
看看,葛大爺已經被拍了一臉血了,我希望您還沒到這地步。
到了這一步,我們其實完成了跟前端頁面制作人員的握手協定:
您寫js的也好,html5的也好,随便整,我傳回的資料格式和内容您也看見了,就這樣,格式不會變,内容也不會出錯。資料怎麼嵌,那是您的事兒,俺就不伺候了。
關于”内容不會出錯“這句,補充一點兒,這是指的前端頁面人員(也許是你自己兼任)無需啟動一大堆複雜的應用程式,比如mysql、memcache,就能調試自己的html或者js代碼,同時,也避免了其他異常(資料庫down了,資料表毀壞了,網絡斷了,memcache連接配接不上了等等等等)對前端開發的幹擾。
特别是debug階段,如果你不能確定哪些是正确的,你就無法找到錯誤的。
很多程式員在debug排查某個問題的時候,最常犯的錯誤就是迷失在一大堆的子產品中間,找不到出路,造成這種情況的根本原因就在于:在當事人看來,每個子產品都是可疑的,不确定的。要麼猜要麼一個個查,精力和時間就此白白浪費。
而對于後端開發人員來說,現在面臨的就隻剩一個任務:讓下面的類方法傳回真實的業務結果吧。
package cn.com.sitefromscrath.service;
import java.util.ArrayList;
import java.util.List;
import cn.com.sitefromscrath.entity.Result;
public class SearchService {
public List search(String keywords) {
List results = new ArrayList();
results.add(new Result("result 1", "something.................."));
results.add(new Result("result 2", "something.................."));
results.add(new Result("result 3", "something.................."));
results.add(new Result("result 4", "something.................."));
return results;
}
}
我們将在下一章讨論這個問題。
WEB開發那些事兒 | ||
第一部分:從需求出發 | ||
一 | 所見即所得 | 這裡從一個靜态html頁面說起,逐漸抽離出展示層面和資料層面的東西。 |
二 | 造飛機的工廠 | 這裡主要說的是工廠方法。當然,工廠不是目的,而是結果,需求才是源起。 |
三 | 春天在哪裡 | 這裡開始扯到了spring和《兒歌三百首》 |
四 | 春天在這裡 | 對spring的吐槽 |
五 | 麥克斯韋妖 | 讓我們充當一次麥克斯韋妖,探測和控制單個子產品/方法的工作 |
六 | 扒皮MVC | MVC模式的得失,開發的時候,别因為迷失才過程裡爾忘記了我們的目的。 |
第二部分:拿起筆來做刀槍 | ||
序 | 序言 | 拿起筆來做刀槍,開發路上當闖将 |
一 | 再造一個dom4j | 标題說明了一切 |
二 | 再造一個spring | 标題說明了一切 |
三 | 再造一個jsp | 标題說明了一切,這裡的 jsp 不是 java server pages,而是java sign pages :) |
四 | 再造一個struts | 标題說明了一切 |
五 | 再造一個lucene | 标題說明了一切,lucene的原理隻需要一句話說清楚,這個就是我喜歡他的原因 |
六 | 再造一個hibernate | 其實我在意的是HQL如何映射到多種sql查詢語言上 |
七 | Final Fantasy | 最終的成品 |