name: zk-mvc-vs-mvvm description: >- Explains and compares the two primary UI design patterns in the ZK framework: Model-View-Controller (MVC) and Model-View-ViewModel (MVVM). It details their implementation and trade-offs. Use when: creating a new feature in a ZK application, refactoring a ZK page, or choosing the appropriate pattern for a new ZK project.
Skill: ZK UI Patterns - MVC vs. MVVM
This skill provides a detailed comparison of the two main architectural patterns for building user interfaces in the ZK framework: Model-View-Controller (MVC) and Model-View-ViewModel (MVVM). Understanding the difference is crucial for writing clean, testable, and maintainable ZK applications.
1. The MVC Pattern (The "Composer" Pattern)
In ZK's MVC approach, the "Controller" is a Java class that extends SelectorComposer. This Composer is directly linked to a ZUL page and is responsible for all interaction logic.
How it Works
- View (ZUL): The ZUL page is a layout of UI components. Each component that needs to be manipulated is given a unique
id. The<window>or root component uses theapplyattribute to specify its Composer class. - Controller (Composer): The Java class is the "brain".
- It uses the
@Wireannotation to get direct references to UI components from the ZUL page, matching variable names to componentids. - It uses
@Listenannotations or special method naming conventions (e.g.,onClick$myButton) to handle UI events. - Inside event handlers, the Composer code manually pulls data from components (e.g.,
myTextbox.getValue()) and manually pushes data back to them.
- It uses the
Key Characteristics
- Pattern: Imperative and event-driven.
- Coupling: High. The Composer is tightly coupled to the View. It needs to know the specific component types and their
ids. - Testability: Difficult. Because the Composer has direct dependencies on UI components, it's hard to unit test its logic without a complex UI testing environment.
2. The MVVM Pattern (The "ViewModel" Pattern)
MVVM is a more modern pattern that promotes a cleaner separation of concerns. The "ViewModel" is a simple POJO (Plain Old Java Object) that exposes data and commands; it has no knowledge of the UI components themselves.
How it Works
- View (ZUL): The ZUL page defines the layout. Instead of
apply, the root component uses theviewModelattribute to link to a ViewModel class. - Data Binding: The
BindComposer(activated by theviewModelattribute) manages the connection.- Component properties (like
valueormodel) are declaratively linked to ViewModel properties using the@bind()annotation. - UI events (like
onClick) are linked to ViewModel methods using the@command()annotation.
- Component properties (like
- ViewModel (POJO): The Java class exposes state (data) through getter/setter properties and behavior (logic) through methods annotated with
@Command. It knows nothing aboutTextboxorButtonobjects.
Key Characteristics
- Pattern: Declarative and data-centric.
- Coupling: Low. The ViewModel is completely decoupled from the View. The same ViewModel could be used with a completely different ZUL layout.
- Testability: Easy. The ViewModel is a POJO, so its logic can be unit tested easily without any UI framework dependencies.
3. Side-by-Side Comparison: Login Form
Let's compare a simple login form implemented in both styles.
MVC Implementation
login-mvc.zul
<window title="Login MVC" border="normal" width="300px"
apply="br.com.zkminsamples.controller.LoginComposer">
<grid>
<rows>
<row>
<label value="Usuário:"/>
<!-- Components need IDs for the Composer -->
<textbox id="txtUsername" width="150px"/>
</row>
<row>
<label value="Senha:"/>
<textbox id="txtPassword" type="password" width="150px"/>
</row>
<row spans="2" align="center">
<!-- The button's event is handled by its ID -->
<button id="btnLogin" label="Entrar"/>
</row>
</rows>
</grid>
</window>
LoginComposer.java
// The Composer is a "Controller" and knows about UI components
public class LoginComposer extends SelectorComposer<Component> {
// Wires components directly from the View by ID
@Wire
private Textbox txtUsername;
@Wire
private Textbox txtPassword;
// Listens for a click on the component with id="btnLogin"
@Listen("onClick = #btnLogin")
public void doLogin() {
// Manually get values from UI components
String username = txtUsername.getValue();
String password = txtPassword.getValue();
// ... authentication logic ...
}
}
MVVM Implementation
login-mvvm.zul
<window title="Login MVVM" border="normal" width="300px"
viewModel="@id('vm') @init('br.com.zkminsamples.viewmodel.LoginViewModel')">
<grid>
<rows>
<row>
<label value="Usuário:"/>
<!-- Value is bound directly to a ViewModel property -->
<textbox width="150px" value="@bind(vm.username)"/>
</row>
<row>
<label value="Senha:"/>
<textbox type="password" width="150px" value="@bind(vm.password)"/>
</row>
<row spans="2" align="center">
<!-- Click event is bound to a ViewModel command -->
<button label="Entrar" command="@command('doLogin')"/>
</row>
</rows>
</grid>
</window>
LoginViewModel.java
// The ViewModel is a POJO and knows nothing about the UI
public class LoginViewModel {
// These properties hold the state (data)
private String username;
private String password;
// Getters and setters are required for data binding
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
// A method exposed as a "command" for the View to call
@Command
public void doLogin() {
// Logic directly uses the class properties, which are
// automatically synced with the UI by the binder.
// No .getValue() calls are needed.
// ... authentication logic using this.username and this.password ...
}
}
Summary of Trade-offs
| Aspect | MVC (Composer) | MVVM (ViewModel) | Recommendation |
|---|---|---|---|
| Coupling | High (Controller depends on View) | Low (ViewModel is independent) | MVVM for maintainability |
| Testability | Difficult (Requires UI context) | Easy (Can unit test ViewModel as POJO) | MVVM for quality assurance |
| Boilerplate | More code for wiring and event listeners | Less code, more declarative bindings | MVVM for productivity |
| Readability | Logic can be scattered in event handlers | Logic is centralized in ViewModel commands | MVVM for clarity |
| Learning Curve | Slightly easier for absolute beginners | Requires understanding data binding concept | MVVM is the modern standard |
Conclusion: For any new ZK project, MVVM is the highly recommended pattern. It leads to cleaner, more maintainable, and more testable code, aligning with modern development practices. MVC should generally be reserved for maintaining legacy code.
References
- Source Material:
zk-min-samplesrepository, modules02-java8-zk8-mvcand03-java8-zk8-mvvm. - ZK Developer's Reference: https://docs.zkoss.org/zk_dev_ref/
- ZK Official Documentation (MVVM Tutorial): https://www.zkoss.org/zkdocs/latest/UI_Patterns/MVVM/Tutorial