美文网首页Android开发Android高级进阶Android开发
Android 开发中的架构模式 -- MVC / MVP /

Android 开发中的架构模式 -- MVC / MVP /

作者: 初壹十五a | 来源:发表于2020-04-17 03:33 被阅读0次

预备知识

了解 Android 基本开发
看完本文可以达到什么程度

了解如何分析一个架构模式
掌握 MVC,MVP,MVVM 架构定义和实现

更多面试内容,面试专题,flutter视频 全套,音视频从0到高手开发。
关注GitHub:https://github.com/xiangjiana/Android-MS
免费获取面试PDF合集
免费提供简历修改建议,获取大厂面试PDF和视频教程

文章概览

一、什么是架构

关于架构的定义,其实在很多书籍和文章中都是不同的,很难做一个统一。这里列举两个定义:

在维基百科里是这样定义的

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。

在 IEEE 软件工程标准词汇中是这样定义的:

架构是以组件、组件之间的关系、组件与环境之间的关系为内容的某一系统的基本组织结构,以及指导上述内容设计与演化的原理。

在看过茫茫多的架构定义以后,我理解的架构是这样的:

1.为了解决特定的问题而提出
2.按照特定的原则将系统整体进行模块/组件/角色的划分
3.建立模块/组件/角色间的沟通机制

具体解释一下,首先是要有特定的问题,没有问题空谈架构,仿佛是空中楼阁,没有实用价值,而对应到不同的问题,会有不同的解决方式。
其次是模块的划分要根据特定的原则,没有原则随意划分,也就无从去评估一个架构的好坏。最后就是模块间的通信机制,让系统成为一个整体.

最后,架构模式,其实更多的是一种思想,一种规则,往往一种架构模式可能会有不同的实现方式,而实现方式之间,只有合适与否,并没有对错之分。

二、如何分析一种架构模式

上面我们介绍了架构的定义,根据这个定义,我们在后面分析架构模式的时候,也会从这三方面进行。

2.1.架构解决了什么问题

知道了架构模式要解决的问题,我们才能针对性的去看,去想其解决方法是否得当,是否合适。

2.2.架构模式是如何划分角色的

架构中最重要的就是角色 / 模块的划分,理解了架构模式中的角色划分,才能更好的理解其结构。

2.3.角色间是如何通信的

角色间的通信也是重要的。相同的角色划分,采用不同的通信方式,往往就构成了不同的架构模式。

角色间通信我们可以理解为数据的流向。在 Android 开发中,通信中的数据可以理解为两种,一种是数据结构,也就是网络请求,本地存储等通信使用的 JavaBean,另一种是事件,也就是控件产生的动作,包括触摸,点击,滑动等等。我们在通信过程中,也主要关注这两种数据。

三、常见的架构模式有哪些

对于我们 Android 开发者来说,常见的架构模式基本上就是 MVC,MVP,MVVM,这三种也是开发 GUI 应用程序常见的模式。

除此之外还有 分层模式,客户端-服务器模式(CS模式),主从模式,管道过滤器模式,事件总线模式 等等。

这篇文章还是具体分析 MVC,MVP,MVVM 这三种架构模式。

四、不使用架构之前 App 是怎么开发的

我们在了解架构的定义以后,可能会想,为什么要用这些架构模式呢?在我们不了解这些模式之前,也是一样的开发。类似设计模式,其实架构模式的目的不是为了让应用软件开发出来,而是让结构更清晰,分工更明确,扩展更方便等等。

我们可以看看,在不使用架构模式之前我们是怎么开发的。
举个简单的栗子,我们界面上有 EditTextTextViewButton 三个控件,要实现的功能也比较简单:

1.EditText 接受用户输入的内容
2.处理用户输入的数据
3.数据处理后输出到 TextView
4.点击 Button 清空用户的输入

界面如下:

我们看看不使用架构模式是怎么开发的,也就是我们一般常用的开发方式:

4.1. 首先在 xml 设计界面

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      android:padding="10dp"
      tools:context=".MainActivity">

      <TextView
          android:id="@+id/titleText"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Normal" />

      <EditText
          android:id="@+id/edit"
          android:layout_width="match_parent"
          android:layout_height="50dp"
          android:textColor="@android:color/darker_gray" />

      <TextView
          android:id="@+id/msgText"
          android:layout_width="wrap_content"
          android:layout_height="30dp"
          android:layout_marginTop="10dp"
          android:text="default msg"
          android:textColor="@android:color/darker_gray" />

      <TextView
          android:id="@+id/clearText"
          android:layout_width="match_parent"
          android:layout_height="30dp"
          android:layout_marginTop="10dp"
          android:background="@color/colorPrimary"
          android:gravity="center"
          android:text="clear"
          android:textColor="@android:color/white" />
  </LinearLayout>

1.在 Activity / Fragment 中获取 View,进行事件监听
2.通过 View 事件获取数据后进行处理
3.设置处理后的数据给 View

代码如下:

  class NormalFragment : Fragment() {
      companion object {
          fun newInstance(): Fragment {
              return NormalFragment()
          }
      }
      private val handler: Handler = Handler()

      override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
          return inflater.inflate(R.layout.architecture, container, false)
      }

      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
          super.onViewCreated(view, savedInstanceState)
          titleText.text = "NORMAL"
          edit.addTextChangedListener(object : TextWatcher {
              override fun afterTextChanged(s: Editable?) {
                  handleData(s.toString())
              }

              override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
              }

              override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
              }
          })
          clearText.setOnClickListener {
              edit.setText("")
          }
      }

      // 数据的处理,真实情况下可能是网络请求,磁盘存取,大量计算逻辑等等
      private fun handleData(data: String) {
          if (TextUtils.isEmpty(data)) {
              msgText.text = "default msg"
              return
          }
          msgText.text = "handle data ..."
          handler.removeCallbacksAndMessages(null)
          // 延迟来模拟网络或者磁盘操作
          handler.postDelayed({
              msgText.text = "handled data: $data"
          }, 3000)
      }
  }

默认开发方式的缺点:

我们来分析一下上面的代码,一个明显的特点就是处理逻辑都集中在了 Activity / Fragment 中,不管是对 View 的操作,还是对数据的处理。带来的问题就是 Activity / Fragment 中逻辑臃肿,后续扩展牵一发而动全身。而且职责划分不清晰,给后续维护也带来了困难。

既然如此,我们看看使用架构模式改造后是什么样子的。

五、MVC 架构

5.1 模式介绍

其实关于 MVC 架构,在不同的框架里,实现会有些差别,这也正说明了架构是一种思想。我们这里选用一种比较主流的实现。

5.1.1. 解决什么问题

我们可以看到,上面不使用架构进行开发,带来的问题是 Activity / Fragment 逻辑臃肿,不利于扩展。所以 MVC 就要解决的问题就是:控制逻辑,数据处理逻辑和界面交互耦合。

这里先插一个题外话,其实我们作为程序员,写代码不仅要实现需求,还要让代码易读,易扩展。这一点,往往也能体现功力,并不是说使用了各种奇技淫巧才是大神。

不知道大家是否有接触过 Java Swing 桌面应用开发,在 Java Swing 中,界面 / 控件的设置,也是用 Java 代码来实现的,如果不采用架构,最后的结果就是控制逻辑,数据处理以及页面展示的代码都集中在一个类中,读者朋友们可以想象一下,这样的代码简直是难以维护

5.1.2. 如何划分角色

为了解决上面的问题,MVC 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-控制器(Controller)。各个部分的功能如下:

  • Model 模型,负责数据的加载和存储。
  • View 视图,负责界面的展示。
  • Controller 控制器,负责逻辑控制。
5.1.3. 如何通信(数据的流向)

我们再看看三者之间是怎么通信的。

在介绍通信之前,我们先解释一下通信中的数据是什么。其实在 Android 开发中,通信数据可以理解为两种,一种是数据结构,也就是网络请求,本地存储等通信使用的 JavaBean,另一种是事件,也就是控件产生的动作,包括触摸,点击,滑动等等。我们在通信过程中,也主要关注这两种数据。

MVC 架构中,View 产生事件,通知到 ControllerController 中进行一系列逻辑处理,之后通知给 Model 去更新数据,Model 更新数据后,再将数据结构通知给 View 去更新界面。

这就是一个完整 MVC 的数据流向

5.2 在 Android 中的具体实现

理解了 MVC 模式,我们看看其具体实现。

其实在 Android 开发中,其本身默认可以理解为 MVC 结构,把 View 放在 xml中与 Java 代码解耦,然后 Activity / Fragment 充当 Controller 进行逻辑控制,但是 Android 本身并没有对 Model 进行划分,所以往往我们会让 Activity / Fragment 充当 ModelController 两个角色。而且往往 xml 中的 View 操作也是在 Activity / Fragment 中,导致有时候 Activity / Fragment 也会充当一些 View 的角色。

所以我们在具体实现过程中,要把职责划分清楚,这里我们让 Fragment 充当 View 的角色,把 ModelController 的逻辑划分清楚。

我们先定义三个接口如下:

  // 数据模型接口,定义了数据模型的操作
  interface IModel {
      fun setView(view: IView)
      // 数据模型处理输入的数据
      fun handleData(data: String)
      // 清空数据
      fun clearData()
  }

  // 视图接口,定义视图的操作
  interface IView {
      fun setController(controller: IController)
      // 数据处理中状态
      fun dataHanding()
      // 数据处理完成,更新界面
      fun onDataHandled(data: String)
  }

  // 控制器接口,定义控制器的逻辑
  interface IController {
      fun setModel(model: IModel)
      // EditText 数据变化,通知控制器
      fun onDataChanged(data: String)
      // 清空按钮点击事件
      fun clearData()
  }

上面三个接口分别定义了 Model,View,Controller的操作。有一点注意的是,根据 MVC 的通信流程,View 需要持有 ControllerController 需要持有 ModelModel 需要持有 View,所以需要暴露相应的接口。

下面我们看看具体的实现:

  • Model 的实现

Model 中对数据的处理是添加了 "handled data: " 前缀,并增加了 3 秒的延迟

  class HandleModel : IModel {
      private var view: IView? = null
      private val handler: Handler = Handler(Looper.getMainLooper())

      override fun setView(view: IView) {
          this.view = view
      }

      // 接受到数据后,进行处理,这里设置了 3 秒的延迟,模拟网络请求处理数据的操作
      override fun handleData(data: String) {
          if (TextUtils.isEmpty(data)) {
              return
          }
          view?.dataHanding()
          handler.removeCallbacksAndMessages(null)
          // 延迟来模拟网络或者磁盘操作
          handler.postDelayed({
              // 数据处理完成,通知 View 更新界面
              view?.onDataHandled("handled data: $data")
          }, 3000)
      }

      // 接收到清空数据的事件,直接清空数据
      override fun clearData() {
          handler.removeCallbacksAndMessages(null)
          // 数据清空后,通知 View 更新界面
          view?.onDataHandled("")
      }
  }
  • Controller 的实现

Controller 的实现比较简单,将操作直接转发给 Model,实际上,对于复杂的业务场景,这里要处理很多业务逻辑。

  class HandleController : IController {
      private var model: IModel? = null

      override fun onDataChanged(data: String) {
          model?.handleData(data)
      }

      override fun clearData() {
          model?.clearData()
      }

      override fun setModel(model: IModel) {
      }
  }
  • View 的实现

这里 Fragment 充当了 View 的角色,主要负责将 View 的事件传递给 Controller,以及接受到 Model 的数据进行界面更新。

  class MVCFragment : Fragment(), IView {

      companion object {
          fun newInstance(): Fragment {
              return MVCFragment()
          }
      }

      private val model: IModel = HandleModel()
      private var controller: IController = HandleController()

      override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            return inflater.inflate(R.layout.architecture, container, false)
      }

      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
          super.onViewCreated(view, savedInstanceState)
          setController(controller)
          model.setView(this)

          titleText.text = "MVC"
          edit.addTextChangedListener(object : TextWatcher {
              override fun afterTextChanged(s: Editable?) {
                  // 通知 Controller 输入的数据产生变化
                  controller?.onDataChanged(s.toString())
              }

              override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
              }

              override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
              }
          })
          clearText.setOnClickListener {
              // 通知 Controller 清空数据事件
              controller?.clearData()
          }
      }

      // Model 数据变化,进行界面更新
      override fun onDataHandled(data: String) {
          if (TextUtils.isEmpty(data)) {
              edit.setText("")
              msgText.text = "default msg"
          } else {
              msgText.text = data
          }
      }

      // Model 数据变化,进行界面更新
      override fun dataHanding() {
          msgText.text = "handle data ..."
      }

      override fun setController(controller: IController) {
          this.controller = controller
      }
  }

这样我们就实现了一个简单的 MVC 结构。

5.3 MVC 架构模式的优缺点
优点:
  • 结构清晰,职责划分清晰
  • 降低耦合
  • 有利于组件重用
缺点:
  • 其实我们上述的示例,已经是经过优化的 MVC 结构了,一般来说,Activity / Fragment 会承担 ViewController 两个角色,就会导致 Activity / Fragment 中代码较多
  • Model 直接操作 ViewView 的修改会导致 ControllerModel 都进行改动
  • 增加了代码结构的复杂性

六、MVP 架构

6.1 模式介绍

6.1.1. 解决什么问题

MVP 要解决的问题和 MVC 大同小异:控制逻辑,数据处理逻辑和界面交互耦合,同时能将 MVC 中的 ViewModel 解耦。

6.1.2. 如何划分角色

MVP 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-控制器(Presenter)。各个部分的功能如下:

  • Model 模型,负责数据的加载和存储。
  • View 视图,负责界面的展示。
  • Presenter 控制器,负责逻辑控制
6.1.3. 如何通信(数据的流向)

我们可以看到,MVP 中的各个角色划分,和 MVC 基本上相似,那么区别在哪里呢?区别就在角色的通信上。

MVPMVC 最大的不同,就是 ViewModel 不相互持有,都通过 Presenter 做中转。View 产生事件,通知给 PresenterPresenter中进行逻辑处理后,通知 Model 更新数据,Model 更新数据后,通知数据结构给 PresenterPresenter 再通知 View 更新界面。

这就是一个完整 MVP 的数据流向。

6.2 在 Android 中的实现

理解了 MVP 之后,我们看一下其具体实现。

首先我们定义三个接口:

  // 模型接口,定义了数据模型的操作
  interface IModel {
      fun setPresenter(presenter: IPresenter)
      // 梳理数据
      fun handleData(data: String)
      // 清除数据
      fun clearData()
  }

   // 视图接口,定义了视图的操作
  interface IView {
      fun setPresenter(presenter: IPresenter)
      // 数据处理中视图
      fun loading()
      // 数据展示
      fun showData(data: String)
  }

  // 控制器,定义了逻辑操作
  interface IPresenter {
      fun setView(view: IView)
      fun setModel(model: IModel)
      // Model 处理完成数据通知 Presenter
      fun dataHandled(data: String)
      // Model 清除数据后通知 Presenter
      fun dataCleared()
      // View 中 EditText 文字变化后通知 Presenter
      fun onTextChanged(text: String)
      // View 中 Button 点击事件通知 Presenter
      fun onClearBtnClicked()
  }

上面定义了 View,Model,Presenter 三个接口,其中 ViewModel 会持有 PresenterPresenter 持有 ViewModel

接着看下接口的实现:

  • Model 的实现
  class HandleModel : IModel {
    private var presenter: IPresenter? = null
    private var handler = Handler(Looper.getMainLooper())

    override fun handleData(data: String) {
        if (TextUtils.isEmpty(data)) {
            return
        }
        handler.removeCallbacksAndMessages(null)
        // 延迟来模拟网络或者磁盘操作
        handler.postDelayed({
            // 数据处理完成,通知 Presenter
            presenter?.dataHandled("handled data: $data")
        }, 3000)
    }

    override fun clearData() {
        handler.removeCallbacksAndMessages(null)
        // 数据清理完成,通知 Presenter
        presenter?.dataCleared()
    }

    override fun setPresenter(presenter: IPresenter) {
        this.presenter = presenter
    }

 }

Model 的实现和前面 MVC 中的实现基本一致,不过在 MVCModel 直接操作 View 进行视图展示,而在 MVP 里,要通知 Presenter 去中转。

  • View 的实现

这里依旧是 Fragment 充当了 View 的角色,主要负责将 View 的事件传递给 Presenter,以及接受到 Presenter 的数据进行界面更新。

  class MVPFragment : Fragment(), IView {

    companion object {
        fun newInstance(): Fragment {
            val presenter = Presenter()
            val fragment = MVPFragment()
            val model = HandleModel()
            fragment.setPresenter(presenter)
            model.setPresenter(presenter)
            presenter.setModel(model)
            presenter.setView(fragment)
            return fragment
        }
    }

    var mpresenter: IPresenter? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.architecture, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        titleText.text = "MVP"

        edit.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                // 传递 文字修改 事件给 Presenter
                mpresenter?.onTextChanged(s.toString())
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            }
        })
        clearText.setOnClickListener {
            // 传递按钮点击事件给 Presenter
            mpresenter?.onClearBtnClicked()
        }
    }

    override fun setPresenter(presenter: IPresenter) {
        this.mpresenter = presenter
    }

    // 展示数据处理中的视图
    override fun loading() {
        msgText.text = "handling data ..."
    }

    // 展示处理后的数据
    override fun showData(data: String) {
        msgText.text = data
    }
 }
  • Presenter 的实现

这里 Presenter 的实现比较简单,没有太多的业务逻辑,实际应用中,这里会进行业务逻辑的处理。

  class Presenter : IPresenter {
      private var model: IModel? = null
      private var view: IView? = null

      override fun setModel(model: IModel) {
          this.model = model
      }

      override fun setView(view: IView) {
          this.view = view
      }

      override fun dataHandled(data: String) {
          view?.showData(data)
      }

      override fun dataCleared() {
          view?.showData("")
      }

      override fun onTextChanged(text: String) {
          view?.loading()
          model?.handleData(text)
      }

      override fun onClearBtnClicked() {
          model?.clearData()
      }
   }

6.3 MVP 架构模式的优缺点

优点:
  • 结构清晰,职责划分清晰
  • 模块间充分解耦
  • 有利于组件的重用
缺点:
  • 会引入大量的接口,导致项目文件数量激增
  • 增大代码结构复杂性

七、MVVM 架构

7.1 模式介绍

7.1.1. 解决什么问题

MVVM 要解决的问题和 MVCMVP 大同小异:控制逻辑,数据处理逻辑和界面交互耦合,并且同时能将 MVC 中的 ViewModel 解耦,还可以把 MVPPresenterView 也解耦。

7.1.2. 如何划分角色

MVVM 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-逻辑(ViewModel)。各个部分的功能如下:

  • Model 模型,负责数据的加载和存储。
  • View 视图,负责界面的展示。
  • ViewModel 控制器,负责逻辑控制。
7.1.3. 如何通信(数据的流向)

我们可以看到,MVP 中的各个角色划分,和 MVCMVP 基本上相似,区别也是在于角色的通信上。

我们上面说到,在 MVP 中,就是 ViewModel 不相互持有,都通过 Presenter 做中转。这样可以使 ViewModel 解耦。

而在 MVVM 中,解耦做的更彻底,ViewModel 也不会持有 View。其中 ViewModel 中的改动,会自动反馈给 View 进行界面更新,而 View中的事件,也会自动反馈给 ViewModel

要达到这个效果,当然要使用一些工具辅助,比较常用的就是 databinding

在 MVVM 中,数据的流向是这样的

View 产生事件,自动通知给 ViewModeViewModel 中进行逻辑处理后,通知 Model 更新数据,Model 更新数据后,通知数据结构给 ViewModelViewModel 自动通知 View 更新界面。

这就是一个完整 MVVM 的数据流向。

7.2 在 Android 中的实现

MVVM 的实现会复杂一点,我们先看下接口的定义:

  // ViewModel 接口,定义了逻辑操作
  interface IViewModel {
      fun setModel(model: IModel)
      fun handleText(text: String?)
      fun clearData()
      fun dataHandled(data: String?)
      fun dataCleared()
  }

  // 模型接口,定义了数据操作
  interface IModel {
      fun setViewModel(viewModel: IViewModel)
      fun handleData(data: String?)
      fun clearData()
  }

MVVM 中的接口只定义了 ViewModelModel,没有 View 接口,是因为 View 是通过 databindViewModel 的。

我们再看看具体实现:

  • Model 实现
    Model 的实现和上面基本一致,就是对数据的处理,处理完成后通知 ViewModel
  class HandleModel : IModel {
      private var viewModel: IViewModel? = null
      private var handler = Handler(Looper.getMainLooper())

      override fun handleData(data: String?) {
          if (TextUtils.isEmpty(data)) {
              return
          }
          handler.removeCallbacksAndMessages(null)
          // 延迟来模拟网络或者磁盘操作
          handler.postDelayed({
              // 数据处理完成通知 ViewModel
              viewModel?.dataHandled("handled data: $data")
          }, 3000)
      }

      override fun clearData() {
          handler.removeCallbacksAndMessages(null)
          // 数据清理完成通知 ViewModel
          viewModel?.dataCleared()
      }

      override fun setViewModel(viewModel: IViewModel) {
          this.viewModel = viewModel
      }
  }
  • ViewModel 实现

ViewModel 的实现要有些不同,我们采用 databind 进行 ViewModelView 的绑定。

其中会定义两个变量,inputText 是和 EditText 双向绑定的数据,handledText 是和 TextView 双向绑定的数据。

EditText 中输入的数据有变化,会通知到 inputText 注册的监听器中,而 handledText 值的改变,会自动显示到界面上。

  class ViewModel : IViewModel {
      private var model: IModel? = null
      // View 绑定的数据,inputText 和 handledText 更新后会自动通知 View 更新界面
      var inputText: MutableLiveData<String> = MutableLiveData()
      var handledText: MutableLiveData<String> = MutableLiveData()

      init {
          // 注册数据监听,数据改变后通知 Model 去处理数据
          inputText.observeForever {
              handleText(it)
          }
          handledText.value = "default msg"
      }

      override fun handleText(text: String?) {
          if (TextUtils.isEmpty(text)) {
              handledText.value = "default msg"
              return
          }
          handledText.value = "handle data ..."
          model?.handleData(text)
      }
  
      // 清空按钮的点击事件绑定
      override fun clearData() {
          model?.clearData()
      }

      override fun setModel(model: IModel) {
          this.model = model
          model.setViewModel(this)
      }

      // Model 数据处理完成,设置 handledText 的值,自动更新到界面
      override fun dataHandled(data: String?) {
          handledText.value = data
      }

      // Model 数据处理完成,设置 inputText 的值,自动更新到界面
      override fun dataCleared() {
          inputText.value = ""
      }
  }
  • View 实现
    看一下 View 中的数据绑定。
  class MVVMFragment : Fragment() {
      companion object {
          fun newInstance(): Fragment {
              return MVVMFragment()
          }
      }
 
      override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
          // 使用 databind 进行数据绑定
          var binding: ArchitectureBindingBinding = DataBindingUtil.inflate(inflater, R.layout.architecture_binding, container, false)
          binding.lifecycleOwner = this
          val viewModel = ViewModel()
          viewModel.setModel(HandleModel())
          binding.viewmodel = viewModel
          return binding.root
      }
  }
  <?xml version="1.0" encoding="utf-8"?>
  <layout 
  xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools">

      <!--定义 View 中绑定的数据-->
      <data>
          <variable
              name="viewmodel"
              type="com.zy.architecture.mvvm.ViewModel" />
      </data>

      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical"
          android:padding="10dp"
          tools:context=".MainActivity">

          <TextView
              android:id="@+id/titleText"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="MVVM" />

          <!--双向绑定 inputText 到 EditText-->
          <EditText
              android:id="@+id/edit"
              android:layout_width="match_parent"
              android:layout_height="50dp"
              android:text="@={viewmodel.inputText}" 
              android:textColor="@android:color/darker_gray" />

          <!--绑定 handledText 到 TextView-->
          <TextView
              android:id="@+id/msgText"
              android:layout_width="wrap_content"
              android:layout_height="30dp"
              android:layout_marginTop="10dp"
              android:text="@{viewmodel.handledText}"
              android:textColor="@android:color/darker_gray" />

          <!--绑定清空数据的点击事件 到 TextView-->
          <TextView
              android:id="@+id/clearText"
              android:layout_width="match_parent"
              android:layout_height="30dp"
              android:layout_marginTop="10dp"
              android:background="@color/colorPrimary"
              android:gravity="center"
              android:onClick="@{() -> viewmodel.clearData()}"
              android:text="clear"
              android:textColor="@android:color/white" />
      </LinearLayout>
  </layout>

通过上面的实现,当 EditText 中文字变化后,会自动修改 inputText 的值,触发 inputText 监听器,此时 ViewModel 将消息传递给 Model 进行处理,Model 数据处理完成后,通知 ViewModel 更新 handledText 的值,自动更新到界面上。

点击清空按钮时,自动调用绑定的点击函数,通知 ViewModel 清空事件,ViewModel 将消息传递给 Model 进行数据清空,Model 数据处理完成后,通知 ViewModel 进行界面更新。

7.3 MVVM 架构模式的优缺点

优点:

  • 结构清晰,职责划分清晰
  • 模块间充分解耦
  • MVP 的基础上,MVVMViewViewModel 也进行了解耦

缺点:

  • Debug 困难,由于 ViewViewModel 解耦,导致 Debug 时难以一眼看出 View 的事件传递
  • 代码复杂性增大

八、架构模式的运用

上面的文章中,我们介绍了 MVC,MVP,MVVM 三种架构模式,以及其简单的实现。这里我们再回过头思考一下,什么时候该使用架构模式呢?

架构模式可以使代码模块清晰,职责分工明确,容易扩展,带来的副作用就是会引入大量的接口,导致代码文件数量激增。

我们在最开始说过,架构模式是用来解决特定的问题的,如果特定的问题在目前阶段不是问题,或者不是主要问题,那么我们可以先不考虑使用架构模式。比如一个功能非常简单,代码量少,而后续又没有扩展的需求,那我们直接使用传统方式进行开发,快速且清晰,完全没有必要为了架构而架构。

对于在开始没有考虑架构模式的代码,后续慢慢去重构,也是一个好的选择。

总结来说就是:架构虽好,可不要贪杯哦~

总结

关于我:

更多面试内容,面试专题,flutter视频 全套,音视频从0到高手开发。
关注GitHub:https://github.com/xiangjiana/Android-MS
免费获取面试PDF合集
免费提供简历修改建议,获取大厂面试PDF

相关文章

网友评论

    本文标题:Android 开发中的架构模式 -- MVC / MVP /

    本文链接:https://www.haomeiwen.com/subject/iizrvhtx.html