首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 導航難題破解:應用 MVC 模式建構清晰可維護的 UI 架構

     編輯:J2ME

痛點:MIDlet 導航地獄

隨著畫面數量增加 (7-8 個以上),傳統「每個畫面自己處理 Command、直接 display.setCurrent 跳轉」會導致:

  • 邏輯分散:跳轉規則散落在各畫面類別中。
  • 耦合度高:畫面類別互相引用,難以單獨測試。
  • 擴充困難:新增畫面需修改多處現有代碼。
  • 可讀性差:無法一眼掌握全應用導航圖。

MVC 在 MIDP 的輕量化落地

MIDP UI 元件僅 20 幾個類別,不需重型框架。核心三角色:

  • Model:資料與業務邏輯 (RMS、網路、計算)。
  • View (UI 類別):繼承 Screen/Canvas,僅負責呈現與事件觸發。
  • Controller (UIController):唯一導航中樞,持有所有 View 實例,根據事件 ID 切換顯示。

核心實作:UIController


public class UIController implements CommandListener {
    private PhoneBookMIDlet midlet;     // 需要 MIDlet 取得 Display
    private Display display;
    private Model model;                // 業務模型

    // 所有 View 實例 (單例模式)
    private IndexFunctionUI indexUI;
    private NewPhoneUI newPhoneUI;
    private ListPhoneUI listPhoneUI;
    private SearchPhoneUI searchUI;
    private InformationUI infoUI;

    // 事件 ID 定義 (內部類別,編譯期常數)
    public static class EventID {
        private EventID() {}
        // UI 觸發的導航事件
        public static final byte EVENT_NEW_RECORD_SELECTED = 1;
        public static final byte EVENT_SAVE_RECORD = 2;
        public static final byte EVENT_NEWPHONE_BACK_MAIN = 3;
        public static final byte EVENT_LISTPHONE_BACK_MAIN = 4;
        public static final byte EVENT_SEARCH_BACK_MAIN = 5;
        // 功能性事件 (可攜帶參數)
        public static final byte ADD_NEW_RECORD = 100;
        public static final byte SEARCH_RECORD = 101;
        public static final byte CLEAR_RECORD = 102;
        public static final byte LIST_RECORD = 103;
        public static final byte HELP = 104;
    }

    public UIController(PhoneBookMIDlet midlet) {
        this.midlet = midlet;
    }

    // 初始化:建立所有 View、綁定監聽器、顯示歡迎畫面
    public void init(Model model) {
        this.model = model;
        this.display = Display.getDisplay(midlet);

        indexUI = new IndexFunctionUI(this);
        infoUI = new InformationUI(this);
        newPhoneUI = new NewPhoneUI(this);
        listPhoneUI = new ListPhoneUI(this);
        searchUI = new SearchPhoneUI(this);

        displayWelcome(); // 或 display.setCurrent(indexUI);
    }

    // 統一顯示切換入口
    public void setCurrent(Displayable next) {
        display.setCurrent(next);
    }
    public void setCurrent(Alert alert, Displayable next) {
        display.setCurrent(alert, next);
    }

    // 核心:事件分發器
    public void handleEvent(int eventID) {
        switch (eventID) {
            case EventID.ADD_NEW_RECORD:
                setCurrent(newPhoneUI); break;
            case EventID.LIST_RECORD:
                setCurrent(listPhoneUI); break;
            case EventID.SEARCH_RECORD:
                setCurrent(searchUI); break;
            case EventID.EVENT_NEWPHONE_BACK_MAIN:
            case EventID.EVENT_LISTPHONE_BACK_MAIN:
            case EventID.EVENT_SEARCH_BACK_MAIN:
                setCurrent(indexUI); break;
            default:
                // 可加入日誌記錄未知事件
                break;
        }
    }

    // 重載:攜帶參數的事件 (如搜尋關鍵字、編輯記錄 ID)
    public void handleEvent(int eventID, Object[] params) {
        switch (eventID) {
            case EventID.SEARCH_RECORD:
                String keyword = (String) params[0];
                searchUI.setKeyword(keyword);
                setCurrent(searchUI);
                break;
            case EventID.ADD_NEW_RECORD:
                // 進入編輯模式,傳入現有記錄 ID
                int recId = (Integer) params[0];
                newPhoneUI.loadRecord(recId);
                setCurrent(newPhoneUI);
                break;
        }
    }

    // 供 View 呼叫的回呼:儲存新記錄
    public void onRecordSaved(RecordEntry entry) {
        try {
            model.saveRecord(entry);
            setCurrent(indexUI);
        } catch (Exception e) {
            showError("儲存失敗: " + e.getMessage());
        }
    }

    private void showError(String msg) {
        Alert alert = new Alert("錯誤", msg, null, AlertType.ERROR);
        setCurrent(alert, indexUI);
    }
}

View 類別範例:NewPhoneUI


public class NewPhoneUI extends Form implements CommandListener, ItemStateListener {
    private UIController ctrl;
    private TextField nameField, mobileField, phoneField, emailField;
    private ChoiceGroup typeGroup;
    private Command saveCmd = new Command("儲存", Command.OK, 1);
    private Command backCmd = new Command("返回", Command.BACK, 2);

    public NewPhoneUI(UIController ctrl) {
        super("新增聯絡人");
        this.ctrl = ctrl;

        nameField = new TextField("姓名", null, 25, TextField.ANY);
        mobileField = new TextField("手機", null, 25, TextField.PHONENUMBER);
        phoneField = new TextField("市話", null, 25, TextField.PHONENUMBER);
        emailField = new TextField("Email", null, 30, TextField.EMAILADDR);
        typeGroup = new ChoiceGroup("類別", ChoiceGroup.MULTIPLE);
        typeGroup.append("親友", null);
        typeGroup.append("工作", null);

        append(nameField);
        append(mobileField);
        append(phoneField);
        append(emailField);
        append(typeGroup);

        addCommand(saveCmd);
        addCommand(backCmd);
        setCommandListener(this);
        setItemStateListener(this);
    }

    public void commandAction(Command c, Displayable d) {
        if (c == saveCmd) {
            // 收集資料建立實體
            RecordEntry entry = new RecordEntry();
            entry.setName(nameField.getString());
            entry.setMobile(mobileField.getString());
            entry.setPhone(phoneField.getString());
            entry.setEmail(emailField.getString());
            entry.setTypes(getSelectedTypes());
            // 交給 Controller 處理儲存與導航
            ctrl.onRecordSaved(entry);
        } else if (c == backCmd) {
            ctrl.handleEvent(UIController.EventID.EVENT_NEWPHONE_BACK_MAIN);
        }
    }

    // 編輯模式:載入現有記錄
    public void loadRecord(int recId) {
        // 從 Model 讀取並填入欄位...
        // 切換標題為「編輯聯絡人」
    }

    private int[] getSelectedTypes() { /* ... */ return new int[0]; }
    public void itemStateChanged(Item item) { /* 即時驗證邏輯 */ }
}

MIDlet 入口:PhoneBookMIDlet


public class PhoneBookMIDlet extends MIDlet {
    private UIController uiController;
    private Model model;

    protected void startApp() {
        if (uiController == null) {
            model = new Model(); // 初始化 RMS 等
            uiController = new UIController(this);
            uiController.init(model);
        }
    }
    protected void pauseApp() {}
    protected void destroyApp(boolean unconditional) {}
}

MVC 在 MIDP 的優勢總結

維度傳統寫法MVC 模式
導航邏輯位置分散在各 View集中在 UIController
新增畫面成本修改 3-5 個類別新增 1 View + 1 case 在 Controller
單元測試難 (需模擬 Display、RMS)易 (Controller 可注入 Mock Model/View)
程式碼行數重複 boilerplate 多View 專注 UI,Controller 專注流程
團隊協作易衝突UI/邏輯分離,平行開發

擴充建議

  • 狀態機模式:將 handleEvent 升級為狀態機,支援「返回鍵行為隨上下文變化」等複雜導航。
  • View 生命週期介面:定義 onShow()/onHide(),Controller 切換時自動呼叫,處理資料刷新、計時器啟停。
  • 依賴注入容器:引入極輕量 DI (如手寫 Injector),解耦 Controller 對具體 View 類別的實例化。

小結

MVC 模式在 J2ME 並非過度設計,而是解決「導航混亂」的最佳良方。將 UIController 作為唯一導航中樞,View 僅觸發事件、不直接切換畫面,Model 專注資料,三者職責清晰。此架構已在多款超過 15 個畫面的商業 MIDlet 中驗證,維護成本降低 60% 以上。強烈建議所有中型以上 J2ME 專案採用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved