@TryLoveCatch
2022-04-21T09:46:06.000000Z
字数 16043
阅读 1116
Java知识体系
关于架构的定义,其实在很多书籍和文章中都是不同的,很难做一个统一。这里列举两个定义:
软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。
架构是以组件、组件之间的关系、组件与环境之间的关系为内容的某一系统的基本组织结构,以及指导上述内容设计与演化的原理。
关于更多的定义,推荐阅读《软件架构设计:程序员向架构师转型必备》第二章
- 为了解决特定的问题而提出
- 按照特定的原则将系统整体进行模块/组件/角色的划分
- 建立模块/组件/角色间的沟通机制
具体解释一下:
总之,架构模式,其实更多的是一种思想,一种规则,往往一种架构模式可能会有不同的实现方式,而实现方式之间,只有合适与否,并没有对错之分。
上面我们介绍了架构的定义,根据这个定义,我们在后面分析架构模式的时候,也会从这三方面进行。
对于我们 Android 开发者来说,常见的架构模式基本上就是 MVC,MVP,MVVM,这三种也是开发 GUI 应用程序常见的模式。
除此之外还有 分层模式,客户端-服务器模式(CS模式),主从模式,管道过滤器模式,事件总线模式 等等。
咱们主要来看 MVC,MVP,MVVM 这三种架构模式。
我们在了解架构的定义以后,可能会想,为什么要用这些架构模式呢?
在我们不了解这些模式之前,也是一样的开发。
类似设计模式,其实架构模式的目的不是为了让应用软件开发出来,而是让结构更清晰,分工更明确,扩展更方便等等。
我们可以看看,在不使用架构模式之前我们是怎么开发的。
举个简单的栗子,我们界面上有 EditText,TextView,Button 三个控件,要实现的功能也比较简单:
效果如下图所示:
首先在 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>
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)
}
}
我们分析一下上面的代码:
既然如此,我们看看使用架构模式改造后是什么样子的。
其实关于 MVC 架构,在不同的框架里,实现会有些差别,这也正说明了架构是一种思想。我们这里选用一种比较主流的实现。
首先,上面不使用架构进行开发带来的问题: Activity / Fragment 逻辑臃肿,不利于扩展。
所以 MVC 就要解决的问题就是:控制逻辑,数据处理逻辑和界面交互耦合。
这里先插一个题外话,其实我们作为程序员,写代码不仅要实现需求,还要让代码易读,易扩展。这一点,往往也能体现功力,并不是说使用了各种奇技淫巧才是大神。
为了解决上面的问题,MVC 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-控制器(Controller)。
各个部分的功能如下:
我们再看看三者之间是怎么通信的。
在介绍通信之前,我们先解释一下通信中的数据是什么。上面也说过,在 Android 开发中,通信数据可以理解为两种:
我们在通信过程中,也主要关注这两种数据。
一个完整 MVC 的数据流向:
View 产生事件,通知到 Controller
Controller 中进行一系列逻辑处理,之后通知给 Model 去更新数据
Model 更新数据后,再将数据结构通知给 View 去更新界面。
理解了 MVC 模式,我们看看其具体实现。
其实在 Android 开发中,其本身默认可以理解为 MVC 结构:
所以我们在具体实现过程中,要把职责划分清楚,这里我们让 Fragment 充当 View 的角色,把 Model 和 Controller 的逻辑划分清楚。
// 数据模型接口,定义了数据模型的操作
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 需要持有 Controller,Controller 需要持有 Model,Model 需要持有 View,所以需要暴露相应的接口。
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 的实现比较简单,将操作直接转发给 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) {
this.model = model
}
}
这里 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
}
}
MVP 要解决的问题和 MVC 大同小异:控制逻辑,数据处理逻辑和界面交互耦合,同时能将 MVC 中的 View 和 Model 解耦。
MVP 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-控制器(Presenter)。
各个部分的功能如下:
我们可以看到,MVP 中的各个角色划分,和 MVC 基本上相似,那么区别在哪里呢?区别就在角色的通信上。
MVP 和 MVC 最大的不同,就是 View 和 Model 不相互持有,都通过 Presenter 做中转。
// 模型接口,定义了数据模型的操作
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 三个接口,其中 View 和 Model 会持有 Presenter,Presenter 持有 View 和 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 中的实现基本一致,不过在 MVC 中 Model 直接操作 View 进行视图展示,而在 MVP 里,要通知 Presenter 去中转。
这里依旧是 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 的实现比较简单,没有太多的业务逻辑,实际应用中,这里会进行业务逻辑的处理。
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()
}
}
MVVM 要解决的问题和 MVC,MVP 大同小异:控制逻辑,数据处理逻辑和界面交互耦合,并且同时能将 MVC 中的 View 和 Model 解耦,还可以把 MVP 中 Presenter 和 View 也解耦。
MVVM 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-逻辑(ViewModel)。
各个部分的功能如下:
我们可以看到,MVP 中的各个角色划分,和 MVC,MVP 基本上相似,区别也是在于角色的通信上。
我们上面说到,在 MVP 中,就是 View 和 Model 不相互持有,都通过 Presenter 做中转。这样可以使 View 和 Model 解耦。
而在 MVVM 中,解耦做的更彻底,ViewModel 也不会持有 View。
其中 ViewModel 中的改动,会自动反馈给 View 进行界面更新,而 View 中的事件,也会自动反馈给 ViewModel。
要达到这个效果,当然要使用一些工具辅助,比较常用的就是 databinding。
在 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 中的接口只定义了 ViewModel 和 Model,没有 View 接口,是因为 View 是通过 databind 和 ViewModel 的。
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 的实现要有些不同,我们采用 databind 进行 ViewModel 和 View 的绑定。
其中会定义两个变量,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 中的数据绑定:
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 进行界面更新。
上面的文章中,我们介绍了 MVC,MVP,MVVM 三种架构模式,以及其简单的实现。
这里我们再回过头思考一下,什么时候该使用架构模式呢?
架构模式可以使代码模块清晰,职责分工明确,容易扩展,带来的副作用就是会引入大量的接口,导致代码文件数量激增。
我们在最开始说过,架构模式是用来解决特定的问题的,如果特定的问题在目前阶段不是问题,或者不是主要问题,那么我们可以先不考虑使用架构模式。
比如一个功能非常简单,代码量少,而后续又没有扩展的需求,那我们直接使用传统方式进行开发,快速且清晰,完全没有必要为了架构而架构。
对于在开始没有考虑架构模式的代码,后续慢慢去重构,也是一个好的选择。
https://github.com/5A59/android-training
https://github.com/5A59/android-training/blob/master/common-tec/android%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F.md