1 MVC模式
MVC模式是一种将软件界面和业务逻辑解耦的设计模式。MVC分别代表Model、View和Controller。Model是业务模型,包含了业务逻辑和业务数据。View是视图,将业务操作的结果(即业务数据)展示给用户。Controller是控制器,接受用户交互,作为用户的代理人,发起业务流程。在MVC模式中,业务流程由Controller驱动,Controller控制Model和View,完成业务流程。在MVC模式下业务流程的执行步骤为:
- 用户操作软件(如点击鼠标),请求执行业务流程。
- Controller接受用户操作事件,委托Model发起业务流程。
- Model执行业务流程,将业务执行结果(业务数据)返回给Controller。
- Controller将业务数据发送给View,委托View进行展示。
MVC模式的控制流程如下:
- 用户 -> Controller
- Controller -> Model
- Controller -> View
MVC模式的数据流程如下:
- Model -> Controller
- Controller -> View
- View -> 用户
2 MVP模式
MVP是Model、View、Presenter三个单词的首字母缩写。MVP和MVC的区别不能简单的理解为将Controller替换为Presenter。实际上MVP将Controller负责接受用户交互的职责分配给了View。同时为了避免Model和View耦合,引入了Presenter。而Model的职责不变,仍然负责处理业务逻辑,记录业务数据。 在MVC中,交互(或控制)和展示是分离的。而在MVP中,交互和展示都由View负责。MVC模式的驱动是Controller,MVP模式的驱动则是View。Presenter不是Controller的简单替代,而是将Model和View解耦,同时也将Model和View联系起来的桥梁。MVP模式下业务流程的执行步骤为:
- 用户操作软件(如点击鼠标),请求执行业务流程。
- View接受用户操作事件,通知Presenter。
- Presenter通知Model发起业务流程。
- Model执行业务流程,将业务执行结果(业务数据)返回给Presenter。
- Presenter将业务数据返回给View,View进行展示。
MVP模式的控制流程如下:
- 用户 -> View
- View -> Presenter
- Presenter -> Model
- Model -> Presenter
- Presenter -> View
MVP模式的数据流程如下:
- View -> Presenter
- Presenter -> Model
- Model -> Presenter
- Presenter -> View
- View -> 用户
3 MVVM模式
MVVM表示Model、View和ViewModel。MVVM是从MVP发展而来的一种模式。在MVP中,Presenter只是作为Model和View之间传递信息的桥梁。而在MVVM中,ViewModel除了作为View和Model的桥梁之外,还承担了部分视图渲染工作,将业务数据转换成更容易渲染的视图数据。
MVVM模式的控制流程如下:
- 用户 -> View
- View -> Viewmodel
- Viewmodel -> Model
- Model -> Viewmodel
- Viewmodel -> View
MVVM模式的数据流程如下:
- View -> Viewmodel
- Viewmodel -> Model
- Model -> Viewmodel
- Viewmodel -> View
- View -> 用户
4 VIPER模式
VIPER是View、Interactor、Presenter、Entity和Router的缩写。VIPER实际上是MVP模式的细化。VIPER的V和P分别对应MVP中的V和P。I、E、R对应MVP中的M。E是保存业务数据的部分,I和R是控制业务流程的,其中R负责业务子流程分派。
5 通用模型
一个系统通常包括三个模型:控制模型负责和用户交互,接受用户指令,将用户指令转化为业务请求发送给业务模型。业务模型执行业务操作,更新业务数据。视图模型负责展示业务数据,即业务数据可视化。通常视图模型会展示业务操作结果。有时为了提高用户体验,视图模型也可以展示业务流程的内部信息,比如流程节点或流程进度。
控制模型会触发业务模型,因此二者联系较为紧密。视图模型负责展示业务数据,至于业务数据是如何产生的,视图模型并不关心。因此视图模型和控制模型、业务模型的关联程度较低。在触摸屏出现之前,控制模型和视图模型是分离的。比如传统手机,按键是控制器,屏幕是视图,二者是分离的。触摸屏出现之后,触摸事件可以作为输入。屏幕既承担了展示数据的职责,也承担的接受用户输入的职责,控制模型和视图模型开始融合,因此发展出了MVP和MVVM模型。在最初的MVP模型中,P只是作为解耦M和V的工具。随着视图模型逐渐变得复杂,不同视图中出现了重复的代码。这些代码是关于如何展示业务数据的,和业务逻辑本身无关,因此不适合放到业务模型中。如果这些代码放到V中,就是MVP模式。如果这些代码放到P中,P就变成了VM,MVP也就变成MVVM模式。

6 示例程序
import java.util.*;
import java.time.*;
import java.time.format.*;
class Model {
public Payload.Output update(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
}
interface View {
void render(Payload.Output output);
}
class ViewHtml implements View {
private DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
public void render(Payload.Output output) {
String time = output.time.format(formatter);
System.out.printf("<p>%s<br/>%s<br/><p>\n", output.name, time);
}
}
class ViewText implements View {
private DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
public void render(Payload.Output output) {
String time = output.time.format(formatter);
System.out.printf("%s\n%s\n", output.name, time);
}
}
class Controller {
private Model model = new Model();
private View view;
public Controller() {
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
view = new ViewText();
break;
case "web":
view = new ViewHtml();
break;
default:
break;
}
}
public void process(Payload.Input input) {
Payload.Output output = model.update(input);
view.render(output);
}
}
public class MvcOop {
private Controller controller = new Controller();
public static void main(String... args) {
new MvcOop().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
controller.process(input);
}
}import java.util.*;
import java.time.*;
import java.time.format.*;
public class MvcPop {
public static void main(String... args) {
new MvcPop().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
controller(input);
}
public void controller(Payload.Input input) {
Payload.Output output = model(input);
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
viewText(output);
break;
case "web":
viewHtml(output);
break;
default:
break;
}
}
public Payload.Output model(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
public void viewText(Payload.Output output) {
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
System.out.printf("%s\n%s\n", output.name, time);
}
public void viewHtml(Payload.Output output) {
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
System.out.printf("<p>%s<br/>%s<br/><p>\n", output.name, time);
}
}import java.util.*;
import java.time.*;
import java.time.format.*;
class Model {
public Payload.Output update(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
}
interface View {
void onInput(Payload.Input input);
void render(Payload.Output output);
}
class ViewHtml implements View {
private Presenter presenter = new Presenter();
private DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
public void onInput(Payload.Input input) {
render(presenter.update(input));
}
public void render(Payload.Output output) {
String time = output.time.format(formatter);
System.out.printf("<p>%s<br/>%s<br/><p>\n", output.name, time);
}
}
class ViewText implements View {
private Presenter presenter = new Presenter();
private DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
public void onInput(Payload.Input input) {
render(presenter.update(input));
}
public void render(Payload.Output output) {
String time = output.time.format(formatter);
System.out.printf("%s\n%s\n", output.name, time);
}
}
class Presenter {
private Model model = new Model();
public Payload.Output update(Payload.Input input) {
return model.update(input);
}
}
public class MvpOop {
private View view;
public MvpOop() {
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
view = new ViewText();
break;
case "web":
view = new ViewHtml();
break;
default:
break;
}
}
public static void main(String... args) {
new MvpOop().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
view.onInput(input);
}
}import java.util.*;
import java.time.*;
import java.time.format.*;
public class MvpPop {
public static void main(String... args) {
new MvpPop().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
viewText(input);
break;
case "web":
viewHtml(input);
break;
default:
break;
}
}
public Payload.Output model(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
public Payload.Output presenter(Payload.Input input) {
return model(input);
}
public void viewText(Payload.Input input) {
Payload.Output output = presenter(input);
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
System.out.printf("%s\n%s\n", output.name, time);
}
public void viewHtml(Payload.Input input) {
Payload.Output output = presenter(input);
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
System.out.printf("<p>%s<br/>%s<br/><p>\n", output.name, time);
}
}import java.util.*;
import java.time.*;
import java.time.format.*;
class Model {
public Payload.Output update(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
}
interface View {
void onInput(Payload.Input input);
void render(Viewmodel.RenderInput renderInput);
}
class ViewHtml implements View {
private Viewmodel viewmodel = new Viewmodel();
public void onInput(Payload.Input input) {
render(viewmodel.update(input));
}
public void render(Viewmodel.RenderInput renderInput) {
System.out.printf("<p>%s<p>\n", renderInput.message);
}
}
class ViewText implements View {
private Viewmodel viewmodel = new Viewmodel();
public void onInput(Payload.Input input) {
render(viewmodel.update(input));
}
public void render(Viewmodel.RenderInput renderInput) {
System.out.printf("%s\n", renderInput.message);
}
}
class Viewmodel {
public static class RenderInput {
public String message;
}
private Model model = new Model();
public RenderInput update(Payload.Input input) {
Payload.Output output = model.update(input);
RenderInput renderInput = new RenderInput();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
renderInput.message = String.format("%s\n%s", output.name, time);
return renderInput;
}
}
public class MvvmOop {
private View view;
public MvvmOop() {
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
view = new ViewText();
break;
case "web":
view = new ViewHtml();
break;
default:
break;
}
}
public static void main(String... args) {
new MvvmOop().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
view.onInput(input);
}
}import java.util.*;
import java.time.*;
import java.time.format.*;
public class MvvmPop {
public static void main(String... args) {
new MvvmPop().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
viewText(input);
break;
case "web":
viewHtml(input);
break;
default:
break;
}
}
public Payload.Output model(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
public String viewmodel(Payload.Input input) {
Payload.Output output = model(input);
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
return String.format("%s\n%s", output.name, time);
}
public void viewText(Payload.Input input) {
String message = viewmodel(input);
System.out.printf("%s\n", message);
}
public void viewHtml(Payload.Input input) {
String message = viewmodel(input);
System.out.printf("<p>%s<p>\n", message);
}
}import java.util.*;
import java.time.*;
import java.time.format.*;
public class NonPattern {
public static void main(String... args) {
new NonPattern().run();
}
public void run() {
Payload.Input input = Payload.getUserInput();
Payload.Output output = process(input);
String mode = System.getProperties().getProperty("mode", "console");
switch (mode) {
case "console":
displayText(output);
break;
case "web":
displayHtml(output);
break;
default:
break;
}
}
private Payload.Output process(Payload.Input input) {
return new Payload.Output(input.name, LocalDateTime.now());
}
private void displayText(Payload.Output output) {
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
System.out.printf("%s\n%s\n", output.name, time);
}
private void displayHtml(Payload.Output output) {
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String time = output.time.format(formatter);
System.out.printf("<p>%s<br/>%s<br/><p>\n", output.name, time);
}
}import java.util.*;
import java.time.*;
public class Payload {
public static class Input {
public String name;
public Input(String aName) {
name = aName;
}
}
public static class Output {
public String name;
public LocalDateTime time;
public Output(String aName, LocalDateTime aTime) {
name = aName;
time = aTime;
}
}
public static Input getUserInput() {
return new Input("Holmes");
}
}$ModeList=@("console", "web")
Function RunDemo([String] $DemoName, [String] $Mode)
{
Write-Host "Compile $DemoName.java"
javac "$DemoName.java"
If ($LastExitCode -eq 0) {
Write-Host "Run $DemoName"
java -cp . "-Dmode=${Mode}" "$DemoName"
}
}
Function RunDemoAll {
Clear-Host
$DemoList=@("NonPattern", "MvcPop", "MvcOop", "MvpPop", "MvpOop", "MvvmPop", "MvvmOop")
ForEach ($Demo in $DemoList)
{
ForEach ($Mode in $ModeList)
{
RunDemo $Demo $Mode
If ($LastExitCode -ne 0) {
Write-Host "ERROR"
Break
}
Else
{
Write-Host "================"
}
}
}
}
If ($Args.length -eq 0)
{
RunDemoAll
}
Else
{
$Demo = $Args[0]
ForEach ($Mode in $ModeList)
{
RunDemo $Demo $Mode
}
}