现代应用架构最佳实践

Google其实从未推出过架构模式,MVX 的设计模式都是广大战友战出来的结果,架构总的目的就是“高内聚,低耦合”,只要满足这一点,项目就会满足:

  1. 好的阅读性
  2. 好的健壮性
  3. 好的扩展性
  4. 等等

近年来,Google推出了真正的自己的架构模式,当然只是建议以及强力建议。

比如他在官网写到: 此现代应用架构鼓励采用以下方法及其他一些方法:

image.png

反应式分层架构是一种软件设计模式,结合了反应式编程和分层架构的优点,旨在构建响应式、高效且易于维护的应用。下面详细解释这个概念。

地址: developer.android.com/topic/archi…

我也是学习这个架构模式。

一、反应式编程

反应式编程是一种声明式编程范式,强调异步数据流和变化的传播。其核心思想是应用程序应该对数据和事件的变化做出反应,而不是依赖于手动处理这些变化。常用的反应式编程工具包括 ReactiveX(RxJava、RxJS 等)和 Kotlin 的 Flow。

二、分层架构

分层架构是一种将应用程序划分为若干层的设计模式,每一层专注于特定的功能或责任,通常包括:

  • 表示层(Presentation Layer):负责用户界面和用户交互。
  • 业务逻辑层(Business Logic Layer):处理应用的核心业务逻辑。
  • 数据层(Data Layer):管理数据存储和访问,通常包括数据库和网络请求。

image.png

三、反应式分层架构

反应式分层架构结合了反应式编程和分层架构的优点,使各层可以通过异步数据流进行通信和交互。这种架构设计可以更好地处理现代应用的复杂性和异步需求,尤其是在移动开发和高并发系统中。

3.1 核心概念和优势
  1. 模块化设计

    • 每一层都独立处理特定的功能,保持高内聚和低耦合。
    • 易于开发、测试和维护。
  2. 异步数据流

    • 数据流动和事件处理采用异步方式,提高应用的响应性和性能。
    • 各层通过数据流进行通信,减少了直接依赖和耦合。
  3. 分离关注点

    • 每一层关注不同的职责,例如表示层处理界面逻辑,业务逻辑层处理应用核心逻辑,数据层处理数据管理。
    • 改善代码的可读性和可维护性。
  4. 灵活性和可扩展性

    • 新功能可以通过添加或修改独立的层或模块来实现,而无需大幅度改变现有代码。
    • 容易适应需求变化和技术更新。
3.2 典型架构示例
  1. 表示层(Presentation Layer)

    • 包含 Activity、Fragment、ViewModel 等界面相关的组件。
    • 通过 ViewModel 与业务逻辑层交互,并观察 LiveData 或 Flow 以获取数据更新。
  2. 业务逻辑层(Business Logic Layer)

    • 包含 UseCase、Interactor 等处理业务逻辑的组件。
    • 接受表示层的请求,执行相应的业务逻辑,并通过异步流返回结果。
  3. 数据层(Data Layer)

    • 包含 Repository、DAO 等负责数据管理的组件。
    • 通过网络或本地数据库获取数据,并将数据流提供给业务逻辑层。

简易分层架构图

image.png

四、举例

假设我们在开发一个新闻阅读应用:

  1. 表示层

    • Activity 显示新闻列表,用户可以点击查看新闻详情。
    • ViewModel 观察新闻列表数据的变化,并更新界面。
  2. 业务逻辑层

    • UseCase 负责从数据层获取新闻列表,并进行必要的业务处理。
    • 返回处理后的数据给 ViewModel。
  3. 数据层

    • Repository 负责从网络或本地数据库获取新闻数据。
    • 使用 Retrofit 进行网络请求,使用 Room 管理本地数据库。

数据流示意

  • 用户在 Activity 中触发刷新新闻列表事件。
  • Activity 调用 ViewModel 的刷新方法。
  • ViewModel 调用 UseCase 以获取新闻数据。
  • UseCase 通过 Repository 获取新闻数据。
  • Repository 返回新闻数据流给 UseCase。
  • UseCase 处理数据后,将结果通过 LiveData 或 Flow 返回给 ViewModel。
  • ViewModel 更新 LiveData,Activity 观察到数据变化并更新界面。

数据流和状态反馈时序图

image.png

4.1 Demo

下面是一个简化的新闻阅读应用代码示例,采用反应式分层架构进行实现。该示例包括表示层、业务逻辑层和数据层,并使用了 Kotlin、ViewModel、LiveData 和 Retrofit。

4.1.1 依赖

首先,添加必要的依赖项到 build.gradle 文件中:

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation "com.squareup.retrofit2:converter-gson:2.9.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
    implementation "androidx.room:room-runtime:2.3.0"
    kapt "androidx.room:room-compiler:2.3.0"
}
4.1.2 数据层
4.1.2.1 数据模型

定义新闻数据模型 NewsArticle:

data class NewsArticle(
    val id: Int,
    val title: String,
    val content: String
)
4.1.2.2 Retrofit API 接口

定义 Retrofit API 接口:

interface NewsApiService {
    @GET("news")
    suspend fun getNews(): List<NewsArticle>
}
4.1.2.3 Retrofit 实例

创建 Retrofit 实例:

object RetrofitInstance {
    private val retrofit by lazy {
        Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    val api: NewsApiService by lazy {
        retrofit.create(NewsApiService::class.java)
    }
}
4.1.2.4 Repository

定义 Repository 以管理数据获取逻辑:

class NewsRepository {
    private val api = RetrofitInstance.api

    suspend fun getNews(): List<NewsArticle> {
        return api.getNews()
    }
}
4.1.3 业务逻辑层
4.1.3.1 UseCase

定义 UseCase 以处理业务逻辑:

class GetNewsUseCase(private val repository: NewsRepository) {
    suspend operator fun invoke(): List<NewsArticle> {
        return repository.getNews()
    }
}
4.1.4 表示层
4.1.4.1 ViewModel

定义 ViewModel 以管理界面相关的数据:

class NewsViewModel(private val getNewsUseCase: GetNewsUseCase) : ViewModel() {
    private val _news = MutableLiveData<List<NewsArticle>>()
    val news: LiveData<List<NewsArticle>> get() = _news

    fun fetchNews() {
        viewModelScope.launch {
            try {
                val newsList = getNewsUseCase()
                _news.postValue(newsList)
            } catch (e: Exception) {
                // 处理异常,例如显示错误消息
            }
        }
    }
}
4.1.4.2 Activity

在 Activity 中使用 ViewModel 和 LiveData:

class NewsActivity : AppCompatActivity() {
    private lateinit var viewModel: NewsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_news)

        // 初始化 Repository 和 UseCase
        val repository = NewsRepository()
        val getNewsUseCase = GetNewsUseCase(repository)

        // 初始化 ViewModel
        val factory = NewsViewModelFactory(getNewsUseCase)
        viewModel = ViewModelProvider(this, factory).get(NewsViewModel::class.java)

        // 观察 LiveData
        viewModel.news.observe(this, Observer { newsList ->
            // 更新 UI,例如显示新闻列表
            // recyclerView.adapter = NewsAdapter(newsList)
        })

        // 获取新闻
        viewModel.fetchNews()
    }
}
4.1.5 说明
  1. 数据层:包括数据模型 NewsArticle、Retrofit API 接口 NewsApiService、Retrofit 实例 RetrofitInstance 和数据仓库 NewsRepository。

  2. 业务逻辑层:包括 GetNewsUseCase,负责调用仓库获取新闻数据。

  3. 表示层:包括 NewsViewModel 和 NewsActivity。NewsViewModel 通过 fetchNews() 方法获取新闻数据并使用 LiveData 将数据暴露给 Activity。NewsActivity 观察 ViewModel 中的 LiveData,并在数据更新时更新界面。

通过这种分层架构,可以将不同层次的职责分开,使代码更易于维护、测试和扩展。

五、 所有层中的单向数据流(略微改动代码 主要说明问题)

我们需要确保数据和状态在各层之间的流动方向是单向的,从数据层到表示层,并通过事件从表示层回到数据层。具体来说,数据从数据源流向界面,界面中的事件再流向业务逻辑层,最终流向数据层

5.1 引入单向数据流(UDF)

我们将通过以下方式实现单向数据流:

  1. 数据流向界面:通过 LiveData 将数据从 ViewModel 传递到界面。
  2. 事件流向数据层:通过 ViewModel 捕获界面事件,并调用 UseCase 和 Repository 处理业务逻辑和数据操作。
5.2 修改后的代码示例

数据层和业务逻辑层保持不变

5.3 表示层
5.3.1 ViewModel

更新 ViewModel 以实现单向数据流:

class NewsViewModel(private val getNewsUseCase: GetNewsUseCase) : ViewModel() {
    private val _news = MutableLiveData<List<NewsArticle>>()
    val news: LiveData<List<NewsArticle>> get() = _news

    private val _loading = MutableLiveData<Boolean>()
    val loading: LiveData<Boolean> get() = _loading

    private val _error = MutableLiveData<String>()
    val error: LiveData<String> get() = _error

    fun fetchNews() {
        _loading.value = true
        viewModelScope.launch {
            try {
                val newsList = getNewsUseCase()
                _news.postValue(newsList)
                _loading.postValue(false)
            } catch (e: Exception) {
                _error.postValue(e.message)
                _loading.postValue(false)
            }
        }
    }
}
5.3.2 Activity

更新 Activity 以实现单向数据流:

class NewsActivity : AppCompatActivity() {
    private lateinit var viewModel: NewsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_news)

        // 初始化 Repository 和 UseCase
        val repository = NewsRepository()
        val getNewsUseCase = GetNewsUseCase(repository)

        // 初始化 ViewModel
        val factory = NewsViewModelFactory(getNewsUseCase)
        viewModel = ViewModelProvider(this, factory).get(NewsViewModel::class.java)

        // 观察 LiveData
        viewModel.news.observe(this, Observer { newsList ->
            // 更新 UI,例如显示新闻列表
            // recyclerView.adapter = NewsAdapter(newsList)
        })

        viewModel.loading.observe(this, Observer { isLoading ->
            // 显示或隐藏加载指示器
        })

        viewModel.error.observe(this, Observer { errorMessage ->
            // 显示错误消息
        })

        // 获取新闻
        viewModel.fetchNews()
    }
}
5.4 说明

引入了以下改动以实现单向数据流:

  1. ViewModel 中的 LiveData 将数据流向界面,包括新闻数据、加载状态和错误信息。

  2. Activity 观察 ViewModel 中的 LiveData 以更新界面,包括新闻列表、加载指示器和错误消息。

  3. 事件流向数据层:当用户触发刷新事件时,Activity 调用 ViewModel 的 fetchNews() 方法,ViewModel 再调用 UseCase 和 Repository 以获取新闻数据。

通过这种方式,我们确保数据和状态的流动方向是单向的,从数据源到界面,并通过事件从界面流向数据层。这种架构设计提高了应用的数据一致性和可维护性。

六、 其他三项

  • 包含状态容器的界面层,用于管理界面的复杂性。
  • 协程和数据流。
  • 依赖项注入最佳实践

其实佬的这篇文章中 juejin.cn/post/702262… 说了MV系列及Google推的MVI架构模式,对于架构的选择没有最好,只有适合自己的项目。 Google在架构模式指南中例举的几项目也不是必须的。

比如: 当我们完成上述架构的时候我们的项目就已经变成一个非常健壮的产品了,看过一句话说是什么架构模式 在大型的项目中一个Activity里面还是3000多行代码,这句话当然是不对的,就算是3万行那也必须是操作UI的3万行,而不能是乱七八糟的3万行。并且越大的项目更要注重架构的设计。

软件开发的长河中过程都是类似的,刚开始一个产品3个人只需要一个月,后面30个人一个小需求需要一个月,《人月神话》中把这总结为软件开发的焦油坑,或许我们都深陷其中,不知在其中。就是“不识庐山真面目,只缘身在此山中”而已。

就拿上面而言,模式是固定的,但是情况是随机的,比如

  1. 公司不让使用kotlin
  2. 公司不让使用jetpack
  3. 等等

那我们就要做出来这种架构的变体,为了满足“高内聚,低耦合”,满足“关注点分离原则”,满足“uncle Bob 提出的六项原则”等等,才能把代码写的漂亮,跳出焦油坑。

我一直处于学了忘,忘了学的过程中,最近利用这种思想设计了Flutter的几个架构项目,发现都是共通的,我们需要掌握的是其中的某一个知识点,只要用到里面实现上面的目的即可。比如对于另外三项,

  1. 使用状态管理界面的复杂性

假设你是databinding 或者viewbinding 那确实没什么必要

  1. 比如你是java开发 那你也用不了协程
  2. 依赖注入,它的目的也是为了满足“关注点分离原则”, 将对的恶创建和注入交给容器,实现控制反转,减少耦合,但是你不用它能不能实现呢?当然能,他不就是人写的吗,最差的情况就是你撸一遍

总结

尽管架构模式可以有多种实现方式,但核心目标始终是实现高内聚和低耦合,从而提高代码的可读性、健壮性和可扩展性。在实际应用中,根据项目的具体需求和约束,灵活应用这些架构理念,才能构建出适合自身项目的最佳架构。

作者:麦客奥德彪
链接:https://juejin.cn/post/7377220397403865142
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值