MVC、MVP和MVVM

166 阅读7分钟

MVC、MVP和MVVM

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
    }
}