跳轉到內容

ZK/如何操作/模式

來自華夏公益教科書

模型-檢視-控制器模式

[編輯 | 編輯原始碼]

ZK 開發指南指出

"ZK doesn't enforce developers to use MVC or other design patterns. 
Whether to use them is the developer's choice"

本維基條目探討了何時可能選擇使用 MVC 模式以及如何實現它。

考慮以下不使用 MVC 模式的程式碼

      <window border="normal">
            <grid width="80%">
            <rows>
                  <row>onChange1 textbox: <textbox id="source1">
<attribute name="onChange">
copy.value = source1.value
</attribute></textbox>
</row>
                  <row>onChange2 datebox: <datebox id="source2">
<attribute name="onChange">
copy.value = source2.value.toString();
</attribute></datebox>
</row>
                  <row>output: <textbox id="copy" readonly="true"/></row>
            </rows>
            </grid>
      </window>

您只需訪問 www.zkoss.org 的“即時演示”頁面即可執行上述程式碼。單擊“嘗試我!”按鈕,將程式碼貼上到編輯器中,然後再次單擊“嘗試我!”以執行程式碼。您將看到,更改文字框或資料框會更新只讀輸出框。

在上面的示例中,由於功能非常簡單,因此很容易將其作為一個整體進行檢視和理解。在實際應用中,處理來自多個元件的資料、呼叫業務服務,然後更新多個不同螢幕元件的事件處理邏輯並不罕見。在複雜的 ZK 應用程式中,定義具有多個彈出視窗、包含頁面檔案和宏元件的桌面也很常見。然後,從導致更新完全不同的 ZUML 原始檔中定義的元件的元件觸發的事件並不少見。閱讀此類程式碼以瞭解應用程式的工作原理可能很困難。隨著應用程式的發展,元件會隨著使用者介面修改而移動到不同的檔案中。事件處理程式程式碼與元件一起移動。這意味著開發人員團隊很難輕鬆地找到程式碼庫中需要更改的位置。

當您需要編寫複雜的的使用者介面時,將所有事件處理程式碼移動到一個或少量控制應用程式使用者介面行為的類中,可以是對時間和精力的良好投資。為此,您可以採用模型-檢視-控制器模式。從 ZK 開發人員的角度來看,MVC 模式由以下內容組成:“模型”是您的業務物件和業務服務。“檢視”是在 ZUML(zul、zhtml)檔案中定義的桌面上的元件集,不包含任何事件處理邏輯。“控制器”是一個純 Java 類,它註冊為桌面上的一個或多個元件的事件監聽器。

以下是上述示例重構後的 MVC 方法

<window border="normal" apply="com.me.MyController">
	<grid width="80%">
		<rows>
			<row>textbox: <textbox id="source1"/></row>
			<row>dateBox: <datebox id="source2"/></row>
			<row>output: <textbox id="copy" readonly="true"/></row>
 		</rows>
	</grid>
</window>

頁面中的視窗具有 apply="com.me.MyController",它引用以下 Java 類

package com.me;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Textbox;

public class MyController extends GenericForwardComposer {

        protected Textbox copy;
        protected Textbox source1;
        protected Datebox source2;
        
    	public void onChange$source1(Event event) throws Exception {
    		copy.setValue(source1.getValue());
    	}

    	public void onChange$source2(Event event) throws Exception {
    		copy.setValue(source2.getValue().toString());
    	}
}

在重構後的示例中,我們看到 zul 檔案(“檢視”)中沒有事件處理程式碼(“控制器”行為)。視窗元件具有“apply”屬性。這指定將 MyController 類的新的例項應用於已初始化的 Window。MyController 類是 GenericForwardComposer 的子類(繼承自 GenericAutowireComposer),以提供在 Window 物件組裝後呼叫的 doAfterCompose 方法。GenericForwardComposer 的行為是使用反射和內省(它查詢公共的 get/set 元件方法或受保護的元件成員變數)將正確的元件自動注入到您的類中。您編寫名為 onXxx$yyy 的事件處理程式方法,GenericFowardComposer 將自動將事件監聽器“public onXxx(Event e)”新增到視窗中的“yyy”元件。在上面的示例中,沒有顯式的模型程式碼或類,因為我們的示例程式碼是一個無狀態應用程式。

另一種稍微不同的方法是不繼承 GenericForwardComposer,而是繼承其父類 GenericAutowireComposer。這為您提供了更多靈活性來選擇自己的事件處理程式名稱

public class MyController extends GenericAutowireComposer {
	protected Textbox copy;
	protected Textbox source1;
	protected Datebox source2;

	public void onSource1(Event event) throws Exception {
		copy.setValue(source1.getValue());
	}

	public void onSource2(Event event) throws Exception {
		copy.setValue(source2.getValue().toString());
	}
}

但是,我們必須在 zul 檔案中新增“forward”屬性,以將元件的 onChange 或 onSelect 事件繫結到我們想象的 MyController 事件處理程式方法

<window id="myWindow" apply="MyController">
        <grid width="80%">
                <rows>
            <row>textbox: <textbox id="source1" forward="onChange=myWindow.onSource1"/></row>
            <row>dateBox: <datebox id="source2" forward="onChange=myWindow.onSource2"/></row>
                        <row>output: <textbox id="copy" readonly="true"/></row>
                </rows>
        </grid>
</window>

在該 zul 中,forward 屬性中的“myWindow.”是可選的,但可以使程式碼更易於理解。在這個簡單的示例中,onSource1 和 onSource2 之類的名稱不會使我們的程式碼具有自記錄性。更現實地說,我們會使用 onUpdateShoppingCart 和 onCompleteCreditCardPayment 之類的事件處理程式名稱,這將證明這種稍微更冗長的方法需要額外部索引鍵入是合理的。

回到我們的 MVC 示例,我們應該考慮控制器在哪裡例項化。如果我們在 apply 屬性中使用類名,例如 apply="com.me.MyController",那麼每次頁面載入、重新載入或使用者在同一個 Web HttpSession 上開啟第二個視窗(FF & IE 中的“Ctrl+n”)時,ZK 都會例項化一個新的控制器物件。重要的是要考慮使用者在同一個會話中開啟第二個視窗時會發生什麼。使用者有兩個並行的 ZK 桌面。然後,您應該小心,在同一個桌面中觸發的事件不能透過共享的控制器物件到達第二個桌面中的事件。例如,以下內容是可行的

<!-- zk will instantiate a new object of the named class -->
<window apply="com.me.MyController">
        <textbox/>
        ....
</window>

同樣,這也是可以的

<zscript>
import com.me.MyController;
...
MyController myController = new MyController(beanHeldInHttpSession);
</zscript>
<!-- We manually instantiated the new object when this page was loaded -->
<window apply="${myController}">
        <textbox/>
        ....
</window>

但是以下內容一般是不安全的

<!-- WARNING DON'T USE sessionScope FOR A COMPOSER IF IT HOLDS REFERENCES TO COMPONENTS -->
<window id="myWindow" border="normal" apply="${sessionScope.myController}">
        <textbox/>
        ....
</window>

請注意上面程式碼中的警告。一個可接受的解決方案是使其相對於元件而言是無狀態的;在事件處理程式中顯式地查詢它們

package com.me;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Textbox;

public class MyController extends GenericForwardComposer {

    	public void onChange$source1(Event event) throws Exception {
		Textbox copy = (Textbox) event.getTarget().getFellow("copy");
		Textbox source1 = (Textbox) event.getTarget().getFellow("source1");
    		copy.setValue(source1.getValue());
    	}

    	public void onChange$source2(Event event) throws Exception {
		Textbox copy = (Textbox) event.getTarget().getFellow("copy");
		Datebox source2 = (Datebox) event.getTarget().getFellow("source2");
    		copy.setValue(source2.getValue().toString());
    	}
}

在此最新版本的程式碼中,可以跨多個視窗共享單個物件,因為它謹慎地使用從給定桌面觸發的事件來解析同一桌面中的元件物件。

華夏公益教科書