Model 2 角色分工
- View (JSP):
Eshop.jsp 主頁面、Cart.jsp 購物車片段 —— 純表現,無業務邏輯
- Controller (Servlet):
ShoppingServlet —— 接收所有請求、協調 Model、決定導向 View
- Model (JavaBean):
CD Bean、Session 中的 Vector<CD> 購物車
Eshop.jsp:主視圖與表單提交
<%@ page session="true" %>
<form name="shoppingForm" action="/examples/servlet/ShoppingServlet" method="POST">
<select name="CD">
<option>Yuan | The Guo Brothers | China | $14.95</option>
<option>Drums of Passion | Babatunde Olatunji | Nigeria | $16.95</option>
...
</select>
<input type="text" name="qty" size="3" value="1">
<input type="hidden" name="action" value="ADD">
<input type="submit" value="Add to Cart">
</form>
<jsp:include page="Cart.jsp" flush="true"/>
- 下拉選單值以
| 分隔:專輯 | 藝人 | 國家 | 價格
- 隱藏欄位
action=ADD 識別操作類型
<jsp:include> 動態包含購物車片段
Cart.jsp:購物車顯示邏輯
<%@ page session="true" import="java.util.*, shopping.CD" %>
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && buylist.size() > 0) {
%>
<table>
<tr><th>ALBUM</th><th>ARTIST</th><th>COUNTRY</th><th>PRICE</th><th>QUANTITY</th><th></th></tr>
<% for (int i=0; i<buylist.size(); i++) {
CD o = (CD) buylist.elementAt(i); %>
<tr>
<td><%= o.getAlbum() %></td> ...
<td>
<form action="/examples/servlet/ShoppingServlet" method="POST">
<input type="hidden" name="action" value="DELETE">
<input type="hidden" name="delindex" value="<%= i %>">
<input type="submit" value="Delete">
</form>
</td>
</tr>
<% } %>
</table>
<form action="/examples/servlet/ShoppingServlet" method="POST">
<input type="hidden" name="action" value="CHECKOUT">
<input type="submit" value="Checkout">
</form>
<% } %>
- 從 Session 取得購物車,為空則不顯示
- 每列商品含獨立刪除表單,攜帶
delindex 識別項目
- 結帳按鈕送出
action=CHECKOUT
ShoppingServlet:中央控制器完整邏輯
public class ShoppingServlet extends HttpServlet {
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session == null) {
res.sendRedirect("https://localhost:8080/error.html");
return;
}
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
String action = req.getParameter("action");
if (!"CHECKOUT".equals(action)) {
if ("DELETE".equals(action)) {
int idx = Integer.parseInt(req.getParameter("delindex"));
buylist.removeElementAt(idx);
} else if ("ADD".equals(action)) {
CD cd = parseCD(req);
if (buylist == null) {
buylist = new Vector();
buylist.addElement(cd);
} else {
boolean merged = false;
for (int i=0; i<buylist.size(); i++) {
CD existing = (CD) buylist.elementAt(i);
if (existing.getAlbum().equals(cd.getAlbum())) {
existing.setQuantity(existing.getQuantity() + cd.getQuantity());
buylist.setElementAt(existing, i);
merged = true; break;
}
}
if (!merged) buylist.addElement(cd);
}
}
session.putValue("shopping.shoppingcart", buylist);
forward("/jsp/shopping/EShop.jsp", req, res);
} else {
float total = 0;
for (int i=0; i<buylist.size(); i++) {
CD o = (CD) buylist.elementAt(i);
total += o.getPrice() * o.getQuantity();
}
total += 0.005f; // 四捨五入至分
String amount = String.valueOf(total).substring(0, String.valueOf(total).indexOf('.')+3);
req.setAttribute("amount", amount);
forward("/jsp/shopping/Checkout.jsp", req, res);
}
}
private CD parseCD(HttpServletRequest req) {
StringTokenizer st = new StringTokenizer(req.getParameter("CD"), "|");
CD cd = new CD();
cd.setAlbum(st.nextToken().trim());
cd.setArtist(st.nextToken().trim());
cd.setCountry(st.nextToken().trim());
cd.setPrice(Float.parseFloat(st.nextToken().replace('$',' ').trim()));
cd.setQuantity(Integer.parseInt(req.getParameter("qty")));
return cd;
}
private void forward(String url, HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
RequestDispatcher rd = getServletContext().getRequestDispatcher(url);
rd.forward(req, res);
}
}
關鍵設計亮點
- 單一入口:所有動作 (ADD/DELETE/CHECKOUT) 由同一 Servlet 根據
action 參數分派
- Session 為 Model 容器:購物車存在 Session,跨請求保持狀態
- Forward 不 Redirect:保持請求屬性 (如
amount),URL 不變,使用者可重新整理
- 智慧合併同款商品:比對專輯名稱,累加數量而非重複列項
- 解析邏輯封裝於私有方法:
parseCD()、forward() 降低 doPost 複雜度