if-else 判斷請求類型。解決之道:MVC + 事件驅動,將「請求處理」轉化為「事件分派」。
| 層 | 職責 | 技術實現 |
|---|---|---|
| Model (模型) | 業務邏輯、資料存取、領域模型 | 純 Java 類 (POJO/JavaBean/EJB) |
| View (視圖) | 使用者介面渲染、表單呈現 | JSP / 模板引擎 |
| Controller (控制器) | 接收事件、協調模型、選擇視圖 | Servlet (核心分派器) |
同一邏輯抽象可對應多種視圖表現:
SingleSelection 抽象 → Radio Button / 下拉選單 / List BoxSubmission 抽象 → Submit Button / 超連結 / 圖片 / JS form.submit()Controller 不關心具體 UI 元素,只處理語義事件。
public class EventDrivenServlet extends HttpServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 統一委派給 act 方法
act(req, resp);
}
protected void act(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 核心分派邏輯
}
}
// 基礎訊息
public abstract class Message {}
// 具體訊息
public class LoginMessage extends Message {
private Map credentials = new HashMap<>();
public void setCredential(String key, String val) { credentials.put(key, val); }
public String getCredential(String key) { return credentials.get(key); }
}
public class ChangePasswordMessage extends Message {
private Map credentials = new HashMap<>();
// ... 同樣結構
}
每個可提交的 UI 元素對應一個 Submission,產生特定 Message 子類實例,攜帶表單資料。
protected void act(HttpServletRequest req, HttpServletResponse resp, Message msg)
throws ServletException, IOException {
// 1. 解析請求 → 建立 Message(此處簡化,實際由前端/攔截器建構)
Message message = buildMessageFromRequest(req);
// 2. 根據訊息類型分派
if (message instanceof LoginMessage) {
handleLogin((LoginMessage) message, req, resp);
} else if (message instanceof ChangePasswordMessage) {
handleChangePassword((ChangePasswordMessage) message, req, resp);
} else {
// 未知訊息處理
}
}
void handleLogin(LoginMessage msg, HttpServletRequest req, HttpServletResponse resp) {
String username = msg.getCredential("username");
String password = msg.getCredential("password");
// 調用 Model 層業務邏輯
UserService service = ServiceFactory.getUserService();
User user = service.authenticate(username, password);
// 3. 結果反映至回應
if (user != null) {
req.getSession().setAttribute("user", user);
forwardToPage(req, resp, "/dashboard.jsp");
} else {
req.setAttribute("error", "帳號或密碼錯誤");
forwardToPage(req, resp, "/login.jsp");
}
}
<form action="EventDrivenServlet" method="post">
<input type="hidden" name="messageType" value="LoginMessage" />
帳號:<input name="username" /><br/>
密碼:<input type="password" name="password" /><br/>
<input type="submit" value="登入" />
</form>
private Message buildMessageFromRequest(HttpServletRequest req) {
String type = req.getParameter("messageType");
try {
Class> clazz = Class.forName("com.example.msg." + type);
Message msg = (Message) clazz.newInstance();
// 將所有參數注入 Message Map
Enumeration names = req.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
if (!"messageType".equals(name)) {
msg.setCredential(name, req.getParameter(name));
}
}
return msg;
} catch (Exception e) {
throw new ServletException("無法建構訊息: " + type, e);
}
}
| 傳統模式 | 事件驅動模式 |
|---|---|
| Servlet 中大量 if-else 判斷 action 參數 | 多型分派,新增功能只需新增 Message 類與 Handler |
| 業務邏輯耦合在 Servlet | Model 純粹化,可獨立單元測試 |
| 參數手工獲取、轉換 | Message 封裝參數映射,自動化程度高 |
| 難以擴展新業務流程 | 開放封閉原則:擴展不修改核心分派器 |
ActionForm + Action 即此模式具體化。@Controller + @RequestMapping + @ModelAttribute 註解化分派。在 JSP/Servlet 基礎上導入事件驅動,核心在於將 HTTP 請求語義化為 Message 物件,以多型分派替代條件判斷,實現 Controller 與 Model 的解耦。此模式是現代 MVC 框架的架構雛形,理解其原理有助於深度掌握框架運作機制,亦可在無法引入框架的輕量專案中直接應用,顯著提升代碼結構品質。